[
  {
    "path": ".github/ISSUE_TEMPLATE/bug_report.yml",
    "content": "name: 🐞 問題回報\ndescription: 回報在 Han1meViewer 中出現的問題\nlabels: [bug]\nbody:\n\n  - type: textarea\n    id: actual-behavior\n    attributes:\n      label: 實際行為\n      description: 請簡短解釋實際發生的事\n      placeholder: |\n        Example:\n          \"我打開 Han1meViewer 發生了...\"\n    validations:\n      required: true\n\n  - type: textarea\n    id: expected-behavior\n    attributes:\n      label: 預期行為\n      description: 請簡短解釋你應該發生的事\n      placeholder: |\n        Example:\n          \"我應該要...\"\n    validations:\n      required: true\n\n  - type: input\n    id: version\n    attributes:\n      label: Han1meViewer version\n      description: 你的**Han1meViewer**版本\n      placeholder: |\n        Example: \"v0.14.0\"\n    validations:\n      required: true\n\n  - type: input\n    id: android-version\n    attributes:\n      label: Android version\n      description: 你的**Android**版本\n      placeholder: |\n        Example: \"Android 13\"\n    validations:\n      required: true\n\n  - type: input\n    id: device\n    attributes:\n      label: Device\n      description: 你的設備型號\n      placeholder: |\n        Example: \"Google Pixel 5\"\n    validations:\n      required: true\n\n  - type: textarea\n    id: other-details\n    attributes:\n      label: 其他\n      placeholder: |\n        其他詳細的說明\n"
  },
  {
    "path": ".github/ISSUE_TEMPLATE/custom.md",
    "content": "---\nname: 自定義問題\nabout: 描述此問題的目的\ntitle: ''\nlabels: ''\nassignees: ''\n\n---\n\n\n"
  },
  {
    "path": ".github/ISSUE_TEMPLATE/feature_request.yml",
    "content": "name: ⭐ 功能請求\ndescription: 建議一個功能來改善 Han1meViewer\nlabels: [ good idea | 好主意 ]\nbody:\n\n  - type: textarea\n    id: feature-description\n    attributes:\n      label: 描述您建議的功能\n      description: 如何改進 Han1meViewer？\n      placeholder: |\n        Example:\n          \"我想要bla bla bla...\"\n    validations:\n      required: true\n\n  - type: textarea\n    id: other-details\n    attributes:\n      label: 其他你想講的\n      description: 比如哪些軟體有您所說的類似功能，我方便偷過來\n      placeholder: |\n        其他補充資訊\n"
  },
  {
    "path": ".github/PULL_REQUEST_TEMPLATE/bug_fix.md",
    "content": "---\nname: \"Bug 修復\"\nabout: \"用於提交 Bug 修復的拉取請求\"\ntitle: \"[BUGFIX] <簡短描述>\"\nlabels: \"bug\"\nassignees: \"\"\n---\n\n### 描述\n\n<!-- 請簡短描述你修復的 Bug -->\n\n### 修復方法\n\n<!-- 請描述你是如何修復這個 Bug 的 -->"
  },
  {
    "path": ".github/PULL_REQUEST_TEMPLATE/feature_addition.md",
    "content": "---\nname: \"新功能添加\"\nabout: \"用於提交新功能的拉取請求\"\ntitle: \"[FEATURE] <簡短描述>\"\nlabels: \"good idea | 好主意\"\nassignees: \"\"\n---\n\n### 功能描述\n\n<!-- 請簡短描述你添加的新功能 -->"
  },
  {
    "path": ".github/PULL_REQUEST_TEMPLATE/submit_h_keyframe.md",
    "content": "---\nname: \"提交關鍵H幀\"\nabout: \"分享你的關鍵H幀\"\ntitle: \"[HKeyframe] <簡短描述>\"\nlabels: \"𝐇Keyframe\"\nassignees: \"\"\n---\n\n### 關鍵H幀描述\n\n<!-- 請簡短描述你添加的新關鍵H幀 -->"
  },
  {
    "path": ".github/dependabot.yml",
    "content": "version: 2\nupdates:\n  - package-ecosystem: gradle\n    directory: \"/\" # Location of package manifests\n    schedule:\n      interval: weekly"
  },
  {
    "path": ".github/workflows/ci.yml",
    "content": "name: CI\n\non:\n  push:\n    branches: [ \"master\" ]\n    paths-ignore:\n      - \"**.md\"\n  workflow_dispatch:\n\npermissions:\n  contents: write\n  actions: write\n\njobs:\n  build:\n\n    runs-on: ubuntu-latest\n\n    steps:\n      - name: Checkout\n        uses: actions/checkout@v4\n\n      - name: Set up JDK 21\n        uses: actions/setup-java@v4\n        with:\n          java-version: '21'\n          distribution: 'temurin'\n          cache: gradle\n\n      - name: Load Google Services File\n        env:\n          DATA: ${{ secrets.HA1_GOOGLE_SERVICES_JSON_BASE64 }}\n        run: echo $DATA | base64 -di > app/google-services.json\n\n      - name: Grant execute permission for gradlew\n        run: chmod +x gradlew\n\n      - name: Build and sign APK with Gradle\n        run: ./gradlew assembleRelease\n        env:\n          HA1_KEYSTORE_PASSWORD: ${{ secrets.HA1_KEYSTORE_PASSWORD }}\n          HA1_GITHUB_TOKEN: ${{ secrets.HA1_GITHUB_TOKEN }}\n          HA1_VERSION_SOURCE: 'ci'\n\n      - name: Upload Artifacts(arm32)\n        uses: actions/upload-artifact@v4\n        with:\n          path: app/build/outputs/apk/release/Han1meViewer-v*_armeabi-v7a.apk\n          name: Han1meViewer-ci-${{ github.sha }}-armeabi-v7a\n\n      - name: Upload Artifacts(arm64)\n        uses: actions/upload-artifact@v4\n        with:\n          path: app/build/outputs/apk/release/Han1meViewer-v*_arm64-v8a.apk\n          name: Han1meViewer-ci-${{ github.sha }}-arm64-v8a\n\n      - name: Upload Artifacts(universal)\n        uses: actions/upload-artifact@v4\n        with:\n          path: app/build/outputs/apk/release/Han1meViewer-v*_universal.apk\n          name: Han1meViewer-ci-${{ github.sha }}-universal\n"
  },
  {
    "path": ".github/workflows/ci_release.yml",
    "content": "name: CI\n\non:\n  push:\n    tags:\n      - \"v*.*.*\"\n\npermissions:\n  contents: write\n  actions: write\n\njobs:\n  build:\n\n    runs-on: ubuntu-latest\n\n    steps:\n      - name: Checkout\n        uses: actions/checkout@v4\n\n      - name: Set up JDK 21\n        uses: actions/setup-java@v4\n        with:\n          java-version: '21'\n          distribution: 'temurin'\n          cache: gradle\n\n      - name: Load Google Services File\n        env:\n          DATA: ${{ secrets.HA1_GOOGLE_SERVICES_JSON_BASE64 }}\n        run: echo $DATA | base64 -di > app/google-services.json\n\n      - name: Grant execute permission for gradlew\n        run: chmod +x gradlew\n\n      - name: Build and sign APK with Gradle\n        run: |\n          ./gradlew assembleRelease\n          echo \"VERSION_CODE=$(find app/build/outputs/apk/release -name '*arm64-v8a.apk' | head -n 1 | grep -oP '(?<=release\\+)\\d+(?=_arm64-v8a)')\" >> $GITHUB_ENV\n        env:\n          HA1_KEYSTORE_PASSWORD: ${{ secrets.HA1_KEYSTORE_PASSWORD }}\n          HA1_GITHUB_TOKEN: ${{ secrets.HA1_GITHUB_TOKEN }}\n          HA1_VERSION_SOURCE: 'release'\n\n      - name: Create release\n        uses: softprops/action-gh-release@v2.0.4\n        with:\n          name: Han1meViewer ${{ github.ref_name }}+${{ env.VERSION_CODE }}\n          files: app/build/outputs/apk/release/Han1meViewer-v*.apk\n\n  changelog:\n    name: Generate Changelog\n    runs-on: windows-latest\n    needs: build\n    steps:\n      - name: Checkout\n        uses: actions/checkout@v4.1.1\n        with:\n          fetch-depth: 0\n          fetch-tags: true\n\n      - name: Generate Changelog\n        id: generate_changelog\n        uses: Night-stars-1/changelog-generator-action@main\n\n      - name: Update Changelog\n        uses: softprops/action-gh-release@v2.0.4\n        with:\n          body: ${{ steps.generate_changelog.outputs.changelog }}\n          make_latest: true\n"
  },
  {
    "path": ".github/workflows/pr_check.yml",
    "content": "name: Pull Request Check\n\non:\n  pull_request:\n    branches: [ \"master\" ]\n    paths-ignore:\n      - \"**.md\"\n\njobs:\n  check:\n\n    runs-on: ubuntu-latest\n\n    steps:\n      - name: Checkout\n        uses: actions/checkout@v4\n\n      - name: Set up JDK 21\n        uses: actions/setup-java@v4\n        with:\n          java-version: '21'\n          distribution: 'temurin'\n          cache: gradle\n\n      - name: Load Google Services File\n        env:\n          DATA: ${{ secrets.HA1_GOOGLE_SERVICES_JSON_BASE64 }}\n        run: echo $DATA | base64 -di > app/google-services.json\n\n      - name: Grant execute permission for gradlew\n        run: chmod +x gradlew\n\n      - name: Build\n        run: ./gradlew build\n"
  },
  {
    "path": ".gitignore",
    "content": "*.iml\n.gradle\n/local.properties\n/.idea/caches\n/.idea/libraries\n/.idea/modules.xml\n/.idea/workspace.xml\n/.idea/navEditor.xml\n/.idea/assetWizardSettings.xml\n.DS_Store\n/build\n/captures\n.externalNativeBuild\n.cxx\nlocal.properties\n"
  },
  {
    "path": ".idea/.gitignore",
    "content": "# Default ignored files\n/shelf/\n/workspace.xml\n"
  },
  {
    "path": ".idea/.name",
    "content": "Han1meViewer"
  },
  {
    "path": ".idea/AndroidProjectSystem.xml",
    "content": "<?xml version=\"1.0\" encoding=\"UTF-8\"?>\n<project version=\"4\">\n  <component name=\"AndroidProjectSystem\">\n    <option name=\"providerId\" value=\"com.android.tools.idea.GradleProjectSystem\" />\n  </component>\n</project>"
  },
  {
    "path": ".idea/appInsightsSettings.xml",
    "content": "<?xml version=\"1.0\" encoding=\"UTF-8\"?>\n<project version=\"4\">\n  <component name=\"AppInsightsSettings\">\n    <option name=\"selectedTabId\" value=\"Firebase Crashlytics\" />\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=\"com.yenaly.han1meviewer.debug\" />\n                  <option name=\"mobileSdkAppId\" value=\"1:713839264129:android:f7ff12857c3c14f3e4e880\" />\n                  <option name=\"projectId\" value=\"han1meviewer\" />\n                  <option name=\"projectNumber\" value=\"713839264129\" />\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    <JetCodeStyleSettings>\n      <option name=\"CODE_STYLE_DEFAULTS\" value=\"KOTLIN_OFFICIAL\" />\n    </JetCodeStyleSettings>\n    <codeStyleSettings language=\"XML\">\n      <option name=\"FORCE_REARRANGE_MODE\" value=\"1\" />\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    </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/compiler.xml",
    "content": "<?xml version=\"1.0\" encoding=\"UTF-8\"?>\n<project version=\"4\">\n  <component name=\"CompilerConfiguration\">\n    <bytecodeTargetLevel target=\"21\" />\n  </component>\n</project>"
  },
  {
    "path": ".idea/dbnavigator.xml",
    "content": "<?xml version=\"1.0\" encoding=\"UTF-8\"?>\n<project version=\"4\">\n  <component name=\"DBNavigator.Project.DataEditorManager\">\n    <record-view-column-sorting-type value=\"BY_INDEX\" />\n    <value-preview-text-wrapping value=\"false\" />\n    <value-preview-pinned value=\"false\" />\n  </component>\n  <component name=\"DBNavigator.Project.DataExportManager\">\n    <export-instructions>\n      <create-header value=\"true\" />\n      <friendly-headers value=\"false\" />\n      <quote-values-containing-separator value=\"true\" />\n      <quote-all-values value=\"false\" />\n      <value-separator value=\"\" />\n      <file-name value=\"\" />\n      <file-location value=\"\" />\n      <scope value=\"GLOBAL\" />\n      <destination value=\"FILE\" />\n      <format value=\"EXCEL\" />\n      <charset value=\"GBK\" />\n    </export-instructions>\n  </component>\n  <component name=\"DBNavigator.Project.DatabaseBrowserManager\">\n    <autoscroll-to-editor value=\"false\" />\n    <autoscroll-from-editor value=\"true\" />\n    <show-object-properties value=\"true\" />\n    <loaded-nodes />\n  </component>\n  <component name=\"DBNavigator.Project.DatabaseEditorStateManager\">\n    <last-used-providers />\n  </component>\n  <component name=\"DBNavigator.Project.DatabaseFileManager\">\n    <open-files />\n  </component>\n  <component name=\"DBNavigator.Project.EditorStateManager\">\n    <last-used-providers />\n  </component>\n  <component name=\"DBNavigator.Project.ExecutionManager\">\n    <retain-sticky-names value=\"false\" />\n  </component>\n  <component name=\"DBNavigator.Project.MethodExecutionManager\">\n    <method-browser />\n    <execution-history>\n      <group-entries value=\"true\" />\n      <execution-inputs />\n    </execution-history>\n    <argument-values-cache />\n  </component>\n  <component name=\"DBNavigator.Project.ObjectDependencyManager\">\n    <last-used-dependency-type value=\"INCOMING\" />\n  </component>\n  <component name=\"DBNavigator.Project.ObjectQuickFilterManager\">\n    <last-used-operator value=\"EQUAL\" />\n    <filters />\n  </component>\n  <component name=\"DBNavigator.Project.ParserDiagnosticsManager\">\n    <diagnostics-history />\n  </component>\n  <component name=\"DBNavigator.Project.ScriptExecutionManager\" clear-outputs=\"true\">\n    <recently-used-interfaces />\n  </component>\n  <component name=\"DBNavigator.Project.Settings\">\n    <connections />\n    <browser-settings>\n      <general>\n        <display-mode value=\"TABBED\" />\n        <navigation-history-size value=\"100\" />\n        <show-object-details value=\"false\" />\n      </general>\n      <filters>\n        <object-type-filter>\n          <object-type name=\"SCHEMA\" enabled=\"true\" />\n          <object-type name=\"USER\" enabled=\"true\" />\n          <object-type name=\"ROLE\" enabled=\"true\" />\n          <object-type name=\"PRIVILEGE\" enabled=\"true\" />\n          <object-type name=\"CHARSET\" enabled=\"true\" />\n          <object-type name=\"TABLE\" enabled=\"true\" />\n          <object-type name=\"VIEW\" enabled=\"true\" />\n          <object-type name=\"MATERIALIZED_VIEW\" enabled=\"true\" />\n          <object-type name=\"NESTED_TABLE\" enabled=\"true\" />\n          <object-type name=\"COLUMN\" enabled=\"true\" />\n          <object-type name=\"INDEX\" enabled=\"true\" />\n          <object-type name=\"CONSTRAINT\" enabled=\"true\" />\n          <object-type name=\"DATASET_TRIGGER\" enabled=\"true\" />\n          <object-type name=\"DATABASE_TRIGGER\" enabled=\"true\" />\n          <object-type name=\"SYNONYM\" enabled=\"true\" />\n          <object-type name=\"SEQUENCE\" enabled=\"true\" />\n          <object-type name=\"PROCEDURE\" enabled=\"true\" />\n          <object-type name=\"FUNCTION\" enabled=\"true\" />\n          <object-type name=\"PACKAGE\" enabled=\"true\" />\n          <object-type name=\"TYPE\" enabled=\"true\" />\n          <object-type name=\"TYPE_ATTRIBUTE\" enabled=\"true\" />\n          <object-type name=\"ARGUMENT\" enabled=\"true\" />\n          <object-type name=\"DIMENSION\" enabled=\"true\" />\n          <object-type name=\"CLUSTER\" enabled=\"true\" />\n          <object-type name=\"DBLINK\" enabled=\"true\" />\n        </object-type-filter>\n      </filters>\n      <sorting>\n        <object-type name=\"COLUMN\" sorting-type=\"NAME\" />\n        <object-type name=\"FUNCTION\" sorting-type=\"NAME\" />\n        <object-type name=\"PROCEDURE\" sorting-type=\"NAME\" />\n        <object-type name=\"ARGUMENT\" sorting-type=\"POSITION\" />\n        <object-type name=\"TYPE ATTRIBUTE\" sorting-type=\"POSITION\" />\n      </sorting>\n      <default-editors>\n        <object-type name=\"VIEW\" editor-type=\"SELECTION\" />\n        <object-type name=\"PACKAGE\" editor-type=\"SELECTION\" />\n        <object-type name=\"TYPE\" editor-type=\"SELECTION\" />\n      </default-editors>\n    </browser-settings>\n    <navigation-settings>\n      <lookup-filters>\n        <lookup-objects>\n          <object-type name=\"SCHEMA\" enabled=\"true\" />\n          <object-type name=\"USER\" enabled=\"false\" />\n          <object-type name=\"ROLE\" enabled=\"false\" />\n          <object-type name=\"PRIVILEGE\" enabled=\"false\" />\n          <object-type name=\"CHARSET\" enabled=\"false\" />\n          <object-type name=\"TABLE\" enabled=\"true\" />\n          <object-type name=\"VIEW\" enabled=\"true\" />\n          <object-type name=\"MATERIALIZED VIEW\" enabled=\"true\" />\n          <object-type name=\"INDEX\" enabled=\"true\" />\n          <object-type name=\"CONSTRAINT\" enabled=\"true\" />\n          <object-type name=\"DATASET TRIGGER\" enabled=\"true\" />\n          <object-type name=\"DATABASE TRIGGER\" enabled=\"true\" />\n          <object-type name=\"SYNONYM\" enabled=\"false\" />\n          <object-type name=\"SEQUENCE\" enabled=\"true\" />\n          <object-type name=\"PROCEDURE\" enabled=\"true\" />\n          <object-type name=\"FUNCTION\" enabled=\"true\" />\n          <object-type name=\"PACKAGE\" enabled=\"true\" />\n          <object-type name=\"TYPE\" enabled=\"true\" />\n          <object-type name=\"DIMENSION\" enabled=\"false\" />\n          <object-type name=\"CLUSTER\" enabled=\"false\" />\n          <object-type name=\"DBLINK\" enabled=\"true\" />\n        </lookup-objects>\n        <force-database-load value=\"false\" />\n        <prompt-connection-selection value=\"true\" />\n        <prompt-schema-selection value=\"true\" />\n      </lookup-filters>\n    </navigation-settings>\n    <dataset-grid-settings>\n      <general>\n        <enable-zooming value=\"true\" />\n        <enable-column-tooltip value=\"true\" />\n      </general>\n      <sorting>\n        <nulls-first value=\"true\" />\n        <max-sorting-columns value=\"4\" />\n      </sorting>\n      <audit-columns>\n        <column-names value=\"\" />\n        <visible value=\"true\" />\n        <editable value=\"false\" />\n      </audit-columns>\n    </dataset-grid-settings>\n    <dataset-editor-settings>\n      <text-editor-popup>\n        <active value=\"false\" />\n        <active-if-empty value=\"false\" />\n        <data-length-threshold value=\"100\" />\n        <popup-delay value=\"1000\" />\n      </text-editor-popup>\n      <values-actions-popup>\n        <show-popup-button value=\"true\" />\n        <element-count-threshold value=\"1000\" />\n        <data-length-threshold value=\"250\" />\n      </values-actions-popup>\n      <general>\n        <fetch-block-size value=\"100\" />\n        <fetch-timeout value=\"30\" />\n        <trim-whitespaces value=\"true\" />\n        <convert-empty-strings-to-null value=\"true\" />\n        <select-content-on-cell-edit value=\"true\" />\n        <large-value-preview-active value=\"true\" />\n      </general>\n      <filters>\n        <prompt-filter-dialog value=\"true\" />\n        <default-filter-type value=\"BASIC\" />\n      </filters>\n      <qualified-text-editor text-length-threshold=\"300\">\n        <content-types>\n          <content-type name=\"Text\" enabled=\"true\" />\n          <content-type name=\"Properties\" enabled=\"true\" />\n          <content-type name=\"XML\" enabled=\"true\" />\n          <content-type name=\"DTD\" enabled=\"true\" />\n          <content-type name=\"HTML\" enabled=\"true\" />\n          <content-type name=\"XHTML\" enabled=\"true\" />\n          <content-type name=\"Java\" enabled=\"true\" />\n          <content-type name=\"SQL\" enabled=\"true\" />\n          <content-type name=\"PL/SQL\" enabled=\"true\" />\n          <content-type name=\"JSON\" enabled=\"true\" />\n          <content-type name=\"JSON5\" enabled=\"true\" />\n          <content-type name=\"Groovy\" enabled=\"true\" />\n          <content-type name=\"AIDL\" enabled=\"true\" />\n          <content-type name=\"YAML\" enabled=\"true\" />\n          <content-type name=\"Manifest\" enabled=\"true\" />\n        </content-types>\n      </qualified-text-editor>\n      <record-navigation>\n        <navigation-target value=\"VIEWER\" />\n      </record-navigation>\n    </dataset-editor-settings>\n    <code-editor-settings>\n      <general>\n        <show-object-navigation-gutter value=\"false\" />\n        <show-spec-declaration-navigation-gutter value=\"true\" />\n        <enable-spellchecking value=\"true\" />\n        <enable-reference-spellchecking value=\"false\" />\n      </general>\n      <confirmations>\n        <save-changes value=\"false\" />\n        <revert-changes value=\"true\" />\n      </confirmations>\n    </code-editor-settings>\n    <code-completion-settings>\n      <filters>\n        <basic-filter>\n          <filter-element type=\"RESERVED_WORD\" id=\"keyword\" selected=\"true\" />\n          <filter-element type=\"RESERVED_WORD\" id=\"function\" selected=\"true\" />\n          <filter-element type=\"RESERVED_WORD\" id=\"parameter\" selected=\"true\" />\n          <filter-element type=\"RESERVED_WORD\" id=\"datatype\" selected=\"true\" />\n          <filter-element type=\"RESERVED_WORD\" id=\"exception\" selected=\"true\" />\n          <filter-element type=\"OBJECT\" id=\"schema\" selected=\"true\" />\n          <filter-element type=\"OBJECT\" id=\"role\" selected=\"true\" />\n          <filter-element type=\"OBJECT\" id=\"user\" selected=\"true\" />\n          <filter-element type=\"OBJECT\" id=\"privilege\" selected=\"true\" />\n          <user-schema>\n            <filter-element type=\"OBJECT\" id=\"table\" selected=\"true\" />\n            <filter-element type=\"OBJECT\" id=\"view\" selected=\"true\" />\n            <filter-element type=\"OBJECT\" id=\"materialized view\" selected=\"true\" />\n            <filter-element type=\"OBJECT\" id=\"index\" selected=\"true\" />\n            <filter-element type=\"OBJECT\" id=\"constraint\" selected=\"true\" />\n            <filter-element type=\"OBJECT\" id=\"trigger\" selected=\"true\" />\n            <filter-element type=\"OBJECT\" id=\"synonym\" selected=\"false\" />\n            <filter-element type=\"OBJECT\" id=\"sequence\" selected=\"true\" />\n            <filter-element type=\"OBJECT\" id=\"procedure\" selected=\"true\" />\n            <filter-element type=\"OBJECT\" id=\"function\" selected=\"true\" />\n            <filter-element type=\"OBJECT\" id=\"package\" selected=\"true\" />\n            <filter-element type=\"OBJECT\" id=\"type\" selected=\"true\" />\n            <filter-element type=\"OBJECT\" id=\"dimension\" selected=\"true\" />\n            <filter-element type=\"OBJECT\" id=\"cluster\" selected=\"true\" />\n            <filter-element type=\"OBJECT\" id=\"dblink\" selected=\"true\" />\n          </user-schema>\n          <public-schema>\n            <filter-element type=\"OBJECT\" id=\"table\" selected=\"false\" />\n            <filter-element type=\"OBJECT\" id=\"view\" selected=\"false\" />\n            <filter-element type=\"OBJECT\" id=\"materialized view\" selected=\"false\" />\n            <filter-element type=\"OBJECT\" id=\"index\" selected=\"false\" />\n            <filter-element type=\"OBJECT\" id=\"constraint\" selected=\"false\" />\n            <filter-element type=\"OBJECT\" id=\"trigger\" selected=\"false\" />\n            <filter-element type=\"OBJECT\" id=\"synonym\" selected=\"false\" />\n            <filter-element type=\"OBJECT\" id=\"sequence\" selected=\"false\" />\n            <filter-element type=\"OBJECT\" id=\"procedure\" selected=\"false\" />\n            <filter-element type=\"OBJECT\" id=\"function\" selected=\"false\" />\n            <filter-element type=\"OBJECT\" id=\"package\" selected=\"false\" />\n            <filter-element type=\"OBJECT\" id=\"type\" selected=\"false\" />\n            <filter-element type=\"OBJECT\" id=\"dimension\" selected=\"false\" />\n            <filter-element type=\"OBJECT\" id=\"cluster\" selected=\"false\" />\n            <filter-element type=\"OBJECT\" id=\"dblink\" selected=\"false\" />\n          </public-schema>\n          <any-schema>\n            <filter-element type=\"OBJECT\" id=\"table\" selected=\"true\" />\n            <filter-element type=\"OBJECT\" id=\"view\" selected=\"true\" />\n            <filter-element type=\"OBJECT\" id=\"materialized view\" selected=\"true\" />\n            <filter-element type=\"OBJECT\" id=\"index\" selected=\"true\" />\n            <filter-element type=\"OBJECT\" id=\"constraint\" selected=\"true\" />\n            <filter-element type=\"OBJECT\" id=\"trigger\" selected=\"true\" />\n            <filter-element type=\"OBJECT\" id=\"synonym\" selected=\"true\" />\n            <filter-element type=\"OBJECT\" id=\"sequence\" selected=\"true\" />\n            <filter-element type=\"OBJECT\" id=\"procedure\" selected=\"true\" />\n            <filter-element type=\"OBJECT\" id=\"function\" selected=\"true\" />\n            <filter-element type=\"OBJECT\" id=\"package\" selected=\"true\" />\n            <filter-element type=\"OBJECT\" id=\"type\" selected=\"true\" />\n            <filter-element type=\"OBJECT\" id=\"dimension\" selected=\"true\" />\n            <filter-element type=\"OBJECT\" id=\"cluster\" selected=\"true\" />\n            <filter-element type=\"OBJECT\" id=\"dblink\" selected=\"true\" />\n          </any-schema>\n        </basic-filter>\n        <extended-filter>\n          <filter-element type=\"RESERVED_WORD\" id=\"keyword\" selected=\"true\" />\n          <filter-element type=\"RESERVED_WORD\" id=\"function\" selected=\"true\" />\n          <filter-element type=\"RESERVED_WORD\" id=\"parameter\" selected=\"true\" />\n          <filter-element type=\"RESERVED_WORD\" id=\"datatype\" selected=\"true\" />\n          <filter-element type=\"RESERVED_WORD\" id=\"exception\" selected=\"true\" />\n          <filter-element type=\"OBJECT\" id=\"schema\" selected=\"true\" />\n          <filter-element type=\"OBJECT\" id=\"user\" selected=\"true\" />\n          <filter-element type=\"OBJECT\" id=\"role\" selected=\"true\" />\n          <filter-element type=\"OBJECT\" id=\"privilege\" selected=\"true\" />\n          <user-schema>\n            <filter-element type=\"OBJECT\" id=\"table\" selected=\"true\" />\n            <filter-element type=\"OBJECT\" id=\"view\" selected=\"true\" />\n            <filter-element type=\"OBJECT\" id=\"materialized view\" selected=\"true\" />\n            <filter-element type=\"OBJECT\" id=\"index\" selected=\"true\" />\n            <filter-element type=\"OBJECT\" id=\"constraint\" selected=\"true\" />\n            <filter-element type=\"OBJECT\" id=\"trigger\" selected=\"true\" />\n            <filter-element type=\"OBJECT\" id=\"synonym\" selected=\"true\" />\n            <filter-element type=\"OBJECT\" id=\"sequence\" selected=\"true\" />\n            <filter-element type=\"OBJECT\" id=\"procedure\" selected=\"true\" />\n            <filter-element type=\"OBJECT\" id=\"function\" selected=\"true\" />\n            <filter-element type=\"OBJECT\" id=\"package\" selected=\"true\" />\n            <filter-element type=\"OBJECT\" id=\"type\" selected=\"true\" />\n            <filter-element type=\"OBJECT\" id=\"dimension\" selected=\"true\" />\n            <filter-element type=\"OBJECT\" id=\"cluster\" selected=\"true\" />\n            <filter-element type=\"OBJECT\" id=\"dblink\" selected=\"true\" />\n          </user-schema>\n          <public-schema>\n            <filter-element type=\"OBJECT\" id=\"table\" selected=\"true\" />\n            <filter-element type=\"OBJECT\" id=\"view\" selected=\"true\" />\n            <filter-element type=\"OBJECT\" id=\"materialized view\" selected=\"true\" />\n            <filter-element type=\"OBJECT\" id=\"index\" selected=\"true\" />\n            <filter-element type=\"OBJECT\" id=\"constraint\" selected=\"true\" />\n            <filter-element type=\"OBJECT\" id=\"trigger\" selected=\"true\" />\n            <filter-element type=\"OBJECT\" id=\"synonym\" selected=\"true\" />\n            <filter-element type=\"OBJECT\" id=\"sequence\" selected=\"true\" />\n            <filter-element type=\"OBJECT\" id=\"procedure\" selected=\"true\" />\n            <filter-element type=\"OBJECT\" id=\"function\" selected=\"true\" />\n            <filter-element type=\"OBJECT\" id=\"package\" selected=\"true\" />\n            <filter-element type=\"OBJECT\" id=\"type\" selected=\"true\" />\n            <filter-element type=\"OBJECT\" id=\"dimension\" selected=\"true\" />\n            <filter-element type=\"OBJECT\" id=\"cluster\" selected=\"true\" />\n            <filter-element type=\"OBJECT\" id=\"dblink\" selected=\"true\" />\n          </public-schema>\n          <any-schema>\n            <filter-element type=\"OBJECT\" id=\"table\" selected=\"true\" />\n            <filter-element type=\"OBJECT\" id=\"view\" selected=\"true\" />\n            <filter-element type=\"OBJECT\" id=\"materialized view\" selected=\"true\" />\n            <filter-element type=\"OBJECT\" id=\"index\" selected=\"true\" />\n            <filter-element type=\"OBJECT\" id=\"constraint\" selected=\"true\" />\n            <filter-element type=\"OBJECT\" id=\"trigger\" selected=\"true\" />\n            <filter-element type=\"OBJECT\" id=\"synonym\" selected=\"true\" />\n            <filter-element type=\"OBJECT\" id=\"sequence\" selected=\"true\" />\n            <filter-element type=\"OBJECT\" id=\"procedure\" selected=\"true\" />\n            <filter-element type=\"OBJECT\" id=\"function\" selected=\"true\" />\n            <filter-element type=\"OBJECT\" id=\"package\" selected=\"true\" />\n            <filter-element type=\"OBJECT\" id=\"type\" selected=\"true\" />\n            <filter-element type=\"OBJECT\" id=\"dimension\" selected=\"true\" />\n            <filter-element type=\"OBJECT\" id=\"cluster\" selected=\"true\" />\n            <filter-element type=\"OBJECT\" id=\"dblink\" selected=\"true\" />\n          </any-schema>\n        </extended-filter>\n      </filters>\n      <sorting enabled=\"true\">\n        <sorting-element type=\"RESERVED_WORD\" id=\"keyword\" />\n        <sorting-element type=\"RESERVED_WORD\" id=\"datatype\" />\n        <sorting-element type=\"OBJECT\" id=\"column\" />\n        <sorting-element type=\"OBJECT\" id=\"table\" />\n        <sorting-element type=\"OBJECT\" id=\"view\" />\n        <sorting-element type=\"OBJECT\" id=\"materialized view\" />\n        <sorting-element type=\"OBJECT\" id=\"index\" />\n        <sorting-element type=\"OBJECT\" id=\"constraint\" />\n        <sorting-element type=\"OBJECT\" id=\"trigger\" />\n        <sorting-element type=\"OBJECT\" id=\"synonym\" />\n        <sorting-element type=\"OBJECT\" id=\"sequence\" />\n        <sorting-element type=\"OBJECT\" id=\"procedure\" />\n        <sorting-element type=\"OBJECT\" id=\"function\" />\n        <sorting-element type=\"OBJECT\" id=\"package\" />\n        <sorting-element type=\"OBJECT\" id=\"type\" />\n        <sorting-element type=\"OBJECT\" id=\"dimension\" />\n        <sorting-element type=\"OBJECT\" id=\"cluster\" />\n        <sorting-element type=\"OBJECT\" id=\"dblink\" />\n        <sorting-element type=\"OBJECT\" id=\"schema\" />\n        <sorting-element type=\"OBJECT\" id=\"role\" />\n        <sorting-element type=\"OBJECT\" id=\"user\" />\n        <sorting-element type=\"RESERVED_WORD\" id=\"function\" />\n        <sorting-element type=\"RESERVED_WORD\" id=\"parameter\" />\n      </sorting>\n      <format>\n        <enforce-code-style-case value=\"true\" />\n      </format>\n    </code-completion-settings>\n    <execution-engine-settings>\n      <statement-execution>\n        <fetch-block-size value=\"100\" />\n        <execution-timeout value=\"20\" />\n        <debug-execution-timeout value=\"600\" />\n        <focus-result value=\"false\" />\n        <prompt-execution value=\"false\" />\n      </statement-execution>\n      <script-execution>\n        <command-line-interfaces />\n        <execution-timeout value=\"300\" />\n      </script-execution>\n      <method-execution>\n        <execution-timeout value=\"30\" />\n        <debug-execution-timeout value=\"600\" />\n        <parameter-history-size value=\"10\" />\n      </method-execution>\n    </execution-engine-settings>\n    <operation-settings>\n      <transactions>\n        <uncommitted-changes>\n          <on-project-close value=\"ASK\" />\n          <on-disconnect value=\"ASK\" />\n          <on-autocommit-toggle value=\"ASK\" />\n        </uncommitted-changes>\n        <multiple-uncommitted-changes>\n          <on-commit value=\"ASK\" />\n          <on-rollback value=\"ASK\" />\n        </multiple-uncommitted-changes>\n      </transactions>\n      <session-browser>\n        <disconnect-session value=\"ASK\" />\n        <kill-session value=\"ASK\" />\n        <reload-on-filter-change value=\"false\" />\n      </session-browser>\n      <compiler>\n        <compile-type value=\"KEEP\" />\n        <compile-dependencies value=\"ASK\" />\n        <always-show-controls value=\"false\" />\n      </compiler>\n      <debugger>\n        <debugger-type value=\"ASK\" />\n        <use-generic-runners value=\"true\" />\n      </debugger>\n    </operation-settings>\n    <ddl-file-settings>\n      <extensions>\n        <mapping file-type-id=\"VIEW\" extensions=\"vw\" />\n        <mapping file-type-id=\"TRIGGER\" extensions=\"trg\" />\n        <mapping file-type-id=\"PROCEDURE\" extensions=\"prc\" />\n        <mapping file-type-id=\"FUNCTION\" extensions=\"fnc\" />\n        <mapping file-type-id=\"PACKAGE\" extensions=\"pkg\" />\n        <mapping file-type-id=\"PACKAGE_SPEC\" extensions=\"pks\" />\n        <mapping file-type-id=\"PACKAGE_BODY\" extensions=\"pkb\" />\n        <mapping file-type-id=\"TYPE\" extensions=\"tpe\" />\n        <mapping file-type-id=\"TYPE_SPEC\" extensions=\"tps\" />\n        <mapping file-type-id=\"TYPE_BODY\" extensions=\"tpb\" />\n      </extensions>\n      <general>\n        <lookup-ddl-files value=\"true\" />\n        <create-ddl-files value=\"false\" />\n        <synchronize-ddl-files value=\"true\" />\n        <use-qualified-names value=\"false\" />\n        <make-scripts-rerunnable value=\"true\" />\n      </general>\n    </ddl-file-settings>\n    <general-settings>\n      <regional-settings>\n        <date-format value=\"MEDIUM\" />\n        <number-format value=\"UNGROUPED\" />\n        <locale value=\"SYSTEM_DEFAULT\" />\n        <use-custom-formats value=\"false\" />\n      </regional-settings>\n      <environment>\n        <environment-types>\n          <environment-type id=\"development\" name=\"Development\" description=\"Development environment\" color=\"-2430209/-12296320\" readonly-code=\"false\" readonly-data=\"false\" />\n          <environment-type id=\"integration\" name=\"Integration\" description=\"Integration environment\" color=\"-2621494/-12163514\" readonly-code=\"true\" readonly-data=\"false\" />\n          <environment-type id=\"production\" name=\"Production\" description=\"Productive environment\" color=\"-11574/-10271420\" readonly-code=\"true\" readonly-data=\"true\" />\n          <environment-type id=\"other\" name=\"Other\" description=\"\" color=\"-1576/-10724543\" readonly-code=\"false\" readonly-data=\"false\" />\n        </environment-types>\n        <visibility-settings>\n          <connection-tabs value=\"true\" />\n          <dialog-headers value=\"true\" />\n          <object-editor-tabs value=\"true\" />\n          <script-editor-tabs value=\"false\" />\n          <execution-result-tabs value=\"true\" />\n        </visibility-settings>\n      </environment>\n    </general-settings>\n  </component>\n  <component name=\"DBNavigator.Project.StatementExecutionManager\">\n    <execution-variables />\n  </component>\n</project>"
  },
  {
    "path": ".idea/deploymentTargetDropDown.xml",
    "content": "<?xml version=\"1.0\" encoding=\"UTF-8\"?>\n<project version=\"4\">\n  <component name=\"deploymentTargetDropDown\">\n    <value>\n      <entry key=\"PreviewActivity\">\n        <State />\n      </entry>\n      <entry key=\"app\">\n        <State />\n      </entry>\n    </value>\n  </component>\n</project>"
  },
  {
    "path": ".idea/deploymentTargetSelector.xml",
    "content": "<?xml version=\"1.0\" encoding=\"UTF-8\"?>\n<project version=\"4\">\n  <component name=\"deploymentTargetSelector\">\n    <selectionStates>\n      <SelectionState runConfigName=\"app\">\n        <option name=\"selectionMode\" value=\"DROPDOWN\" />\n        <DropdownSelection timestamp=\"2025-07-23T14:50:30.320520800Z\">\n          <Target type=\"DEFAULT_BOOT\">\n            <handle>\n              <DeviceId pluginId=\"PhysicalDevice\" identifier=\"serial=cd2b3532\" />\n            </handle>\n          </Target>\n        </DropdownSelection>\n        <DialogSelection />\n      </SelectionState>\n    </selectionStates>\n  </component>\n</project>"
  },
  {
    "path": ".idea/dictionaries/wrzg8.xml",
    "content": "<component name=\"ProjectDictionaryState\">\n  <dictionary name=\"wrzg8\" />\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        <compositeConfiguration>\n          <compositeBuild compositeDefinitionSource=\"SCRIPT\">\n            <builds>\n              <build path=\"$PROJECT_DIR$/buildSrc\" name=\"buildSrc\">\n                <projects>\n                  <project path=\"$PROJECT_DIR$/buildSrc\" />\n                </projects>\n              </build>\n            </builds>\n          </compositeBuild>\n        </compositeConfiguration>\n        <option name=\"testRunner\" value=\"CHOOSE_PER_TEST\" />\n        <option name=\"externalProjectPath\" value=\"$PROJECT_DIR$\" />\n        <option name=\"gradleJvm\" value=\"jbr-21\" />\n        <option name=\"modules\">\n          <set>\n            <option value=\"$PROJECT_DIR$\" />\n            <option value=\"$PROJECT_DIR$/app\" />\n            <option value=\"$PROJECT_DIR$/buildSrc\" />\n            <option value=\"$PROJECT_DIR$/yenaly_libs\" />\n          </set>\n        </option>\n        <option name=\"resolveExternalAnnotations\" value=\"false\" />\n      </GradleProjectSettings>\n    </option>\n  </component>\n</project>"
  },
  {
    "path": ".idea/inspectionProfiles/Project_Default.xml",
    "content": "<component name=\"InspectionProjectProfileManager\">\n  <profile version=\"1.0\">\n    <option name=\"myName\" value=\"Project Default\" />\n    <inspection_tool class=\"NullableBooleanElvis\" enabled=\"false\" level=\"WEAK WARNING\" enabled_by_default=\"false\" />\n    <inspection_tool class=\"ReplaceCollectionCountWithSize\" enabled=\"true\" level=\"WEAK WARNING\" enabled_by_default=\"true\" />\n  </profile>\n</component>"
  },
  {
    "path": ".idea/kotlinc.xml",
    "content": "<?xml version=\"1.0\" encoding=\"UTF-8\"?>\n<project version=\"4\">\n  <component name=\"Kotlin2JvmCompilerArguments\">\n    <option name=\"jvmTarget\" value=\"1.8\" />\n  </component>\n  <component name=\"KotlinJpsPluginSettings\">\n    <option name=\"version\" value=\"2.0.20\" />\n  </component>\n</project>"
  },
  {
    "path": ".idea/ktlint-plugin.xml",
    "content": "<?xml version=\"1.0\" encoding=\"UTF-8\"?>\n<project version=\"4\">\n  <component name=\"KtLint plugin\">\n    <ktlintMode>DISABLED</ktlintMode>\n    <formatOnSave>false</formatOnSave>\n  </component>\n</project>"
  },
  {
    "path": ".idea/migrations.xml",
    "content": "<?xml version=\"1.0\" encoding=\"UTF-8\"?>\n<project version=\"4\">\n  <component name=\"ProjectMigrations\">\n    <option name=\"MigrateToGradleLocalJavaHome\">\n      <set>\n        <option value=\"$PROJECT_DIR$\" />\n      </set>\n    </option>\n  </component>\n</project>"
  },
  {
    "path": ".idea/misc.xml",
    "content": "<project version=\"4\">\n  <component name=\"DesignSurface\">\n    <option name=\"filePathToZoomLevelMap\">\n      <map>\n        <entry key=\"..\\:/Users/wrzg8/.gradle/caches/transforms-3/5289e4e06c8c1345f256b44f2a8bf24b/transformed/jetified-about-2.5.1/res/layout/about_page_main_activity.xml\" value=\"0.268\" />\n        <entry key=\"..\\:/Users/wrzg8/.gradle/caches/transforms-3/7db9194aba099019cadb4a1513f39d10/transformed/jetified-XPopup-2.8.3/res/layout/_xpopup_bottom_popup_view.xml\" value=\"0.22573749465583584\" />\n        <entry key=\"..\\:/Users/wrzg8/.gradle/caches/transforms-3/87e9ff2b1f11a88e5e2d5c256d23de02/transformed/jetified-MaterialSearchBar-0.8.5/res/drawable-mdpi-v4/ic_menu_animated.xml\" value=\"0.2455\" />\n        <entry key=\"..\\:/Users/wrzg8/.gradle/caches/transforms-3/87e9ff2b1f11a88e5e2d5c256d23de02/transformed/jetified-MaterialSearchBar-0.8.5/res/layout/item_last_request.xml\" value=\"0.36\" />\n        <entry key=\"..\\:/Users/wrzg8/.gradle/caches/transforms-3/87e9ff2b1f11a88e5e2d5c256d23de02/transformed/jetified-MaterialSearchBar-0.8.5/res/layout/searchbar.xml\" value=\"0.3\" />\n        <entry key=\"..\\:/Users/wrzg8/.gradle/caches/transforms-3/bc7aadf64a90ff594b2f090b0cdd700e/transformed/jetified-jiaozivideoplayer-7.7.0/res/drawable/jz_bottom_progress.xml\" value=\"0.139\" />\n        <entry key=\"..\\:/Users/wrzg8/.gradle/caches/transforms-3/bc7aadf64a90ff594b2f090b0cdd700e/transformed/jetified-jiaozivideoplayer-7.7.0/res/drawable/jz_click_back_tiny_selector.xml\" value=\"0.107\" />\n        <entry key=\"..\\:/Users/wrzg8/.gradle/caches/transforms-3/bc7aadf64a90ff594b2f090b0cdd700e/transformed/jetified-jiaozivideoplayer-7.7.0/res/layout/jz_dialog_brightness.xml\" value=\"0.275\" />\n        <entry key=\"..\\:/Users/wrzg8/.gradle/caches/transforms-3/bc7aadf64a90ff594b2f090b0cdd700e/transformed/jetified-jiaozivideoplayer-7.7.0/res/layout/jz_dialog_progress.xml\" value=\"0.275\" />\n        <entry key=\"..\\:/Users/wrzg8/.gradle/caches/transforms-3/bc7aadf64a90ff594b2f090b0cdd700e/transformed/jetified-jiaozivideoplayer-7.7.0/res/layout/jz_dialog_volume.xml\" value=\"0.275\" />\n        <entry key=\"..\\:/Users/wrzg8/.gradle/caches/transforms-3/bc7aadf64a90ff594b2f090b0cdd700e/transformed/jetified-jiaozivideoplayer-7.7.0/res/layout/jz_layout_clarity.xml\" value=\"0.275\" />\n        <entry key=\"..\\:/Users/wrzg8/.gradle/caches/transforms-3/bc7aadf64a90ff594b2f090b0cdd700e/transformed/jetified-jiaozivideoplayer-7.7.0/res/layout/jz_layout_clarity_item.xml\" value=\"0.275\" />\n        <entry key=\"..\\:/Users/wrzg8/.gradle/caches/transforms-3/bc7aadf64a90ff594b2f090b0cdd700e/transformed/jetified-jiaozivideoplayer-7.7.0/res/layout/jz_layout_std.xml\" value=\"0.3\" />\n        <entry key=\"..\\:/Users/wrzg8/AndroidStudioProjects/Han1meViewer/app/src/main/res/drawable/ic_baseline_clear_all_24.xml\" value=\"0.139\" />\n        <entry key=\"..\\:/Users/wrzg8/AndroidStudioProjects/Han1meViewer/app/src/main/res/drawable/ic_baseline_comment_24.xml\" value=\"0.2455\" />\n        <entry key=\"..\\:/Users/wrzg8/AndroidStudioProjects/Han1meViewer/app/src/main/res/drawable/ic_baseline_delete_24.xml\" value=\"0.137\" />\n        <entry key=\"..\\:/Users/wrzg8/AndroidStudioProjects/Han1meViewer/app/src/main/res/drawable/ic_baseline_download_24.xml\" value=\"0.2455\" />\n        <entry key=\"..\\:/Users/wrzg8/AndroidStudioProjects/Han1meViewer/app/src/main/res/drawable/ic_baseline_favorite_24.xml\" value=\"0.139\" />\n        <entry key=\"..\\:/Users/wrzg8/AndroidStudioProjects/Han1meViewer/app/src/main/res/drawable/ic_baseline_favorite_border_24.xml\" value=\"0.139\" />\n        <entry key=\"..\\:/Users/wrzg8/AndroidStudioProjects/Han1meViewer/app/src/main/res/drawable/ic_baseline_help_24.xml\" value=\"0.137\" />\n        <entry key=\"..\\:/Users/wrzg8/AndroidStudioProjects/Han1meViewer/app/src/main/res/drawable/ic_baseline_info_24.xml\" value=\"0.137\" />\n        <entry key=\"..\\:/Users/wrzg8/AndroidStudioProjects/Han1meViewer/app/src/main/res/drawable/ic_baseline_language_24.xml\" value=\"0.137\" />\n        <entry key=\"..\\:/Users/wrzg8/AndroidStudioProjects/Han1meViewer/app/src/main/res/drawable/ic_baseline_menu_24.xml\" value=\"0.137\" />\n        <entry key=\"..\\:/Users/wrzg8/AndroidStudioProjects/Han1meViewer/app/src/main/res/drawable/ic_baseline_more_time_24.xml\" value=\"0.137\" />\n        <entry key=\"..\\:/Users/wrzg8/AndroidStudioProjects/Han1meViewer/app/src/main/res/drawable/ic_baseline_reply_24.xml\" value=\"0.137\" />\n        <entry key=\"..\\:/Users/wrzg8/AndroidStudioProjects/Han1meViewer/app/src/main/res/drawable/ic_baseline_share_24.xml\" value=\"0.139\" />\n        <entry key=\"..\\:/Users/wrzg8/AndroidStudioProjects/Han1meViewer/app/src/main/res/drawable/ic_baseline_tag_24.xml\" value=\"0.137\" />\n        <entry key=\"..\\:/Users/wrzg8/AndroidStudioProjects/Han1meViewer/app/src/main/res/drawable/ic_baseline_update_24.xml\" value=\"0.137\" />\n        <entry key=\"..\\:/Users/wrzg8/AndroidStudioProjects/Han1meViewer/app/src/main/res/drawable/ic_baseline_watch_later_24.xml\" value=\"0.139\" />\n        <entry key=\"..\\:/Users/wrzg8/AndroidStudioProjects/Han1meViewer/app/src/main/res/drawable/ic_outline_watch_later_24.xml\" value=\"0.2455\" />\n        <entry key=\"..\\:/Users/wrzg8/AndroidStudioProjects/Han1meViewer/app/src/main/res/layout/activity_login.xml\" value=\"0.132\" />\n        <entry key=\"..\\:/Users/wrzg8/AndroidStudioProjects/Han1meViewer/app/src/main/res/layout/activity_main.xml\" value=\"0.10389055151774262\" />\n        <entry key=\"..\\:/Users/wrzg8/AndroidStudioProjects/Han1meViewer/app/src/main/res/layout/activity_preview.xml\" value=\"0.20172257479601088\" />\n        <entry key=\"..\\:/Users/wrzg8/AndroidStudioProjects/Han1meViewer/app/src/main/res/layout/activity_preview_comment.xml\" value=\"0.22488242838820008\" />\n        <entry key=\"..\\:/Users/wrzg8/AndroidStudioProjects/Han1meViewer/app/src/main/res/layout/activity_search.xml\" value=\"0.268\" />\n        <entry key=\"..\\:/Users/wrzg8/AndroidStudioProjects/Han1meViewer/app/src/main/res/layout/activity_settings.xml\" value=\"0.22573749465583584\" />\n        <entry key=\"..\\:/Users/wrzg8/AndroidStudioProjects/Han1meViewer/app/src/main/res/layout/activity_video.xml\" value=\"0.10560068405301411\" />\n        <entry key=\"..\\:/Users/wrzg8/AndroidStudioProjects/Han1meViewer/app/src/main/res/layout/fragment_comment.xml\" value=\"0.268\" />\n        <entry key=\"..\\:/Users/wrzg8/AndroidStudioProjects/Han1meViewer/app/src/main/res/layout/fragment_comment_reply.xml\" value=\"0.10389055151774262\" />\n        <entry key=\"..\\:/Users/wrzg8/AndroidStudioProjects/Han1meViewer/app/src/main/res/layout/fragment_home_page.xml\" value=\"0.10389055151774262\" />\n        <entry key=\"..\\:/Users/wrzg8/AndroidStudioProjects/Han1meViewer/app/src/main/res/layout/fragment_list_only.xml\" value=\"0.1564102564102564\" />\n        <entry key=\"..\\:/Users/wrzg8/AndroidStudioProjects/Han1meViewer/app/src/main/res/layout/fragment_page_list.xml\" value=\"0.22488242838820008\" />\n        <entry key=\"..\\:/Users/wrzg8/AndroidStudioProjects/Han1meViewer/app/src/main/res/layout/fragment_tab_view_pager_only.xml\" value=\"0.1\" />\n        <entry key=\"..\\:/Users/wrzg8/AndroidStudioProjects/Han1meViewer/app/src/main/res/layout/fragment_tag_selector.xml\" value=\"0.2606837606837607\" />\n        <entry key=\"..\\:/Users/wrzg8/AndroidStudioProjects/Han1meViewer/app/src/main/res/layout/fragment_video_introduction.xml\" value=\"0.36\" />\n        <entry key=\"..\\:/Users/wrzg8/AndroidStudioProjects/Han1meViewer/app/src/main/res/layout/fragment_watch_history.xml\" value=\"0.3\" />\n        <entry key=\"..\\:/Users/wrzg8/AndroidStudioProjects/Han1meViewer/app/src/main/res/layout/item_hanime_download.xml\" value=\"0.36\" />\n        <entry key=\"..\\:/Users/wrzg8/AndroidStudioProjects/Han1meViewer/app/src/main/res/layout/item_hanime_downloaded.xml\" value=\"0.3\" />\n        <entry key=\"..\\:/Users/wrzg8/AndroidStudioProjects/Han1meViewer/app/src/main/res/layout/item_hanime_preview_news.xml\" value=\"0.3\" />\n        <entry key=\"..\\:/Users/wrzg8/AndroidStudioProjects/Han1meViewer/app/src/main/res/layout/item_hanime_preview_news_pic.xml\" value=\"0.10389055151774262\" />\n        <entry key=\"..\\:/Users/wrzg8/AndroidStudioProjects/Han1meViewer/app/src/main/res/layout/item_hanime_video.xml\" value=\"0.10389055151774262\" />\n        <entry key=\"..\\:/Users/wrzg8/AndroidStudioProjects/Han1meViewer/app/src/main/res/layout/item_hanime_video_simplified.xml\" value=\"0.10389055151774262\" />\n        <entry key=\"..\\:/Users/wrzg8/AndroidStudioProjects/Han1meViewer/app/src/main/res/layout/item_tag_chip.xml\" value=\"0.10389055151774262\" />\n        <entry key=\"..\\:/Users/wrzg8/AndroidStudioProjects/Han1meViewer/app/src/main/res/layout/item_tag_chip_group.xml\" value=\"0.22573749465583584\" />\n        <entry key=\"..\\:/Users/wrzg8/AndroidStudioProjects/Han1meViewer/app/src/main/res/layout/item_tag_selector.xml\" value=\"0.1\" />\n        <entry key=\"..\\:/Users/wrzg8/AndroidStudioProjects/Han1meViewer/app/src/main/res/layout/item_video_comment.xml\" value=\"0.44000000000000006\" />\n        <entry key=\"..\\:/Users/wrzg8/AndroidStudioProjects/Han1meViewer/app/src/main/res/layout/item_video_tag_chip.xml\" value=\"0.10389055151774262\" />\n        <entry key=\"..\\:/Users/wrzg8/AndroidStudioProjects/Han1meViewer/app/src/main/res/layout/item_watch_history.xml\" value=\"0.44000000000000006\" />\n        <entry key=\"..\\:/Users/wrzg8/AndroidStudioProjects/Han1meViewer/app/src/main/res/layout/layout_custom_search_bar.xml\" value=\"0.44000000000000006\" />\n        <entry key=\"..\\:/Users/wrzg8/AndroidStudioProjects/Han1meViewer/app/src/main/res/layout/layout_empty_view.xml\" value=\"0.132\" />\n        <entry key=\"..\\:/Users/wrzg8/AndroidStudioProjects/Han1meViewer/app/src/main/res/layout/layout_search_bar.xml\" value=\"0.44000000000000006\" />\n        <entry key=\"..\\:/Users/wrzg8/AndroidStudioProjects/Han1meViewer/app/src/main/res/layout/nav_header_ability.xml\" value=\"0.22573749465583584\" />\n        <entry key=\"..\\:/Users/wrzg8/AndroidStudioProjects/Han1meViewer/app/src/main/res/layout/pop_up_comment.xml\" value=\"0.4\" />\n        <entry key=\"..\\:/Users/wrzg8/AndroidStudioProjects/Han1meViewer/app/src/main/res/layout/pop_up_hanime_search_tag.xml\" value=\"0.10389055151774262\" />\n        <entry key=\"..\\:/Users/wrzg8/AndroidStudioProjects/Han1meViewer/app/src/main/res/layout/widget_search_bar.xml\" value=\"0.4378947368421053\" />\n        <entry key=\"..\\:/Users/wrzg8/AndroidStudioProjects/Han1meViewer/app/src/main/res/menu/menu_main_bnv.xml\" value=\"0.22573749465583584\" />\n        <entry key=\"..\\:/Users/wrzg8/AndroidStudioProjects/Han1meViewer/app/src/main/res/menu/menu_main_nv.xml\" value=\"0.4\" />\n        <entry key=\"..\\:/Users/wrzg8/AndroidStudioProjects/Han1meViewer/app/src/main/res/menu/menu_main_toolbar.xml\" value=\"0.22573749465583584\" />\n        <entry key=\"..\\:/Users/wrzg8/AndroidStudioProjects/Han1meViewer/app/src/main/res/menu/menu_my_list_toolbar.xml\" value=\"0.268\" />\n        <entry key=\"..\\:/Users/wrzg8/AndroidStudioProjects/Han1meViewer/app/src/main/res/menu/menu_preview_toolbar.xml\" value=\"0.22573749465583584\" />\n        <entry key=\"..\\:/Users/wrzg8/AndroidStudioProjects/Han1meViewer/app/src/main/res/menu/menu_search_toolbar.xml\" value=\"0.10389055151774262\" />\n        <entry key=\"..\\:/Users/wrzg8/AndroidStudioProjects/Han1meViewer/app/src/main/res/menu/menu_watch_history_toolbar.xml\" value=\"0.4\" />\n        <entry key=\"..\\:/Users/wrzg8/AndroidStudioProjects/Han1meViewer/app/src/main/res/xml/settings_home.xml\" value=\"0.2\" />\n        <entry key=\"..\\:/Users/wrzg8/AndroidStudioProjects/Han1meViewer/yenaly_libs/src/main/res/layout/yenaly_activity_settings.xml\" value=\"0.22573749465583584\" />\n        <entry key=\"..\\:/Users/wrzg8/AndroidStudioProjects/Hanime1/app/src/main/res/drawable/gradient_black_transparent.xml\" value=\"0.137\" />\n        <entry key=\"..\\:/Users/wrzg8/AndroidStudioProjects/Hanime1/app/src/main/res/drawable/ic_baseline_access_time_24.xml\" value=\"0.137\" />\n        <entry key=\"..\\:/Users/wrzg8/AndroidStudioProjects/Hanime1/app/src/main/res/drawable/ic_baseline_arrow_back_24.xml\" value=\"0.139\" />\n        <entry key=\"..\\:/Users/wrzg8/AndroidStudioProjects/Hanime1/app/src/main/res/drawable/ic_baseline_cancel_24.xml\" value=\"0.137\" />\n        <entry key=\"..\\:/Users/wrzg8/AndroidStudioProjects/Hanime1/app/src/main/res/drawable/ic_baseline_checklist_24.xml\" value=\"0.256\" />\n        <entry key=\"..\\:/Users/wrzg8/AndroidStudioProjects/Hanime1/app/src/main/res/drawable/ic_baseline_newspaper_24.xml\" value=\"0.139\" />\n        <entry key=\"..\\:/Users/wrzg8/AndroidStudioProjects/Hanime1/app/src/main/res/drawable/ic_baseline_play_circle_outline_24.xml\" value=\"0.137\" />\n        <entry key=\"..\\:/Users/wrzg8/AndroidStudioProjects/Hanime1/app/src/main/res/drawable/ic_baseline_search_24.xml\" value=\"0.256\" />\n        <entry key=\"..\\:/Users/wrzg8/AndroidStudioProjects/Hanime1/app/src/main/res/drawable/ic_baseline_thumb_down_alt_24.xml\" value=\"0.139\" />\n        <entry key=\"..\\:/Users/wrzg8/AndroidStudioProjects/Hanime1/app/src/main/res/drawable/ic_baseline_thumb_down_off_alt_24.xml\" value=\"0.139\" />\n        <entry key=\"..\\:/Users/wrzg8/AndroidStudioProjects/Hanime1/app/src/main/res/drawable/ic_baseline_thumb_up_alt_24.xml\" value=\"0.139\" />\n        <entry key=\"..\\:/Users/wrzg8/AndroidStudioProjects/Hanime1/app/src/main/res/drawable/ic_baseline_thumb_up_off_alt_24.xml\" value=\"0.139\" />\n        <entry key=\"..\\:/Users/wrzg8/AndroidStudioProjects/Hanime1/app/src/main/res/drawable/search_bar.xml\" value=\"0.256\" />\n        <entry key=\"..\\:/Users/wrzg8/AndroidStudioProjects/Hanime1/app/src/main/res/drawable/shape_reply_show_bottom_dialog.xml\" value=\"0.139\" />\n        <entry key=\"..\\:/Users/wrzg8/AndroidStudioProjects/Hanime1/app/src/main/res/drawable/shape_tag_selector.xml\" value=\"0.136\" />\n        <entry key=\"..\\:/Users/wrzg8/AndroidStudioProjects/Hanime1/app/src/main/res/drawable/shape_title_mask.xml\" value=\"0.139\" />\n        <entry key=\"..\\:/Users/wrzg8/AndroidStudioProjects/Hanime1/app/src/main/res/layout/activity_hanime_preview.xml\" value=\"0.4\" />\n        <entry key=\"..\\:/Users/wrzg8/AndroidStudioProjects/Hanime1/app/src/main/res/layout/activity_main.xml\" value=\"0.1265625\" />\n        <entry key=\"..\\:/Users/wrzg8/AndroidStudioProjects/Hanime1/app/src/main/res/layout/activity_preview.xml\" value=\"0.20153985507246377\" />\n        <entry key=\"..\\:/Users/wrzg8/AndroidStudioProjects/Hanime1/app/src/main/res/layout/activity_search.xml\" value=\"0.3\" />\n        <entry key=\"..\\:/Users/wrzg8/AndroidStudioProjects/Hanime1/app/src/main/res/layout/activity_video.xml\" value=\"0.3\" />\n        <entry key=\"..\\:/Users/wrzg8/AndroidStudioProjects/Hanime1/app/src/main/res/layout/fragment_home_page.xml\" value=\"0.212784588441331\" />\n        <entry key=\"..\\:/Users/wrzg8/AndroidStudioProjects/Hanime1/app/src/main/res/layout/fragment_home_page_main.xml\" value=\"0.1\" />\n        <entry key=\"..\\:/Users/wrzg8/AndroidStudioProjects/Hanime1/app/src/main/res/layout/fragment_search.xml\" value=\"0.27395833333333336\" />\n        <entry key=\"..\\:/Users/wrzg8/AndroidStudioProjects/Hanime1/app/src/main/res/layout/fragment_tag_selector.xml\" value=\"0.3516642547033285\" />\n        <entry key=\"..\\:/Users/wrzg8/AndroidStudioProjects/Hanime1/app/src/main/res/layout/fragment_video_comment.xml\" value=\"0.275\" />\n        <entry key=\"..\\:/Users/wrzg8/AndroidStudioProjects/Hanime1/app/src/main/res/layout/fragment_video_comment_reply.xml\" value=\"0.36\" />\n        <entry key=\"..\\:/Users/wrzg8/AndroidStudioProjects/Hanime1/app/src/main/res/layout/fragment_video_introduction.xml\" value=\"0.44000000000000006\" />\n        <entry key=\"..\\:/Users/wrzg8/AndroidStudioProjects/Hanime1/app/src/main/res/layout/item_hanime_preview_news.xml\" value=\"0.4\" />\n        <entry key=\"..\\:/Users/wrzg8/AndroidStudioProjects/Hanime1/app/src/main/res/layout/item_hanime_preview_news_pic.xml\" value=\"0.22488242838820008\" />\n        <entry key=\"..\\:/Users/wrzg8/AndroidStudioProjects/Hanime1/app/src/main/res/layout/item_hanime_simplified.xml\" value=\"0.27395833333333336\" />\n        <entry key=\"..\\:/Users/wrzg8/AndroidStudioProjects/Hanime1/app/src/main/res/layout/item_hanime_video.xml\" value=\"0.44000000000000006\" />\n        <entry key=\"..\\:/Users/wrzg8/AndroidStudioProjects/Hanime1/app/src/main/res/layout/item_hanime_video_simplified.xml\" value=\"0.4\" />\n        <entry key=\"..\\:/Users/wrzg8/AndroidStudioProjects/Hanime1/app/src/main/res/layout/item_home_page.xml\" value=\"0.3\" />\n        <entry key=\"..\\:/Users/wrzg8/AndroidStudioProjects/Hanime1/app/src/main/res/layout/item_tag_chip.xml\" value=\"0.4\" />\n        <entry key=\"..\\:/Users/wrzg8/AndroidStudioProjects/Hanime1/app/src/main/res/layout/item_tag_chip_group.xml\" value=\"0.268\" />\n        <entry key=\"..\\:/Users/wrzg8/AndroidStudioProjects/Hanime1/app/src/main/res/layout/item_tag_selector.xml\" value=\"0.12552083333333333\" />\n        <entry key=\"..\\:/Users/wrzg8/AndroidStudioProjects/Hanime1/app/src/main/res/layout/item_video_comment.xml\" value=\"0.268\" />\n        <entry key=\"..\\:/Users/wrzg8/AndroidStudioProjects/Hanime1/app/src/main/res/layout/item_video_tag_chip.xml\" value=\"0.12552083333333333\" />\n        <entry key=\"..\\:/Users/wrzg8/AndroidStudioProjects/Hanime1/app/src/main/res/layout/pop_up_hanime_search_tag.xml\" value=\"0.268\" />\n        <entry key=\"..\\:/Users/wrzg8/AndroidStudioProjects/Hanime1/app/src/main/res/menu/menu_main_bnv.xml\" value=\"0.275\" />\n        <entry key=\"..\\:/Users/wrzg8/AndroidStudioProjects/Hanime1/app/src/main/res/menu/menu_main_toolbar.xml\" value=\"0.27395833333333336\" />\n        <entry key=\"..\\:/Users/wrzg8/AndroidStudioProjects/Hanime1/app/src/main/res/menu/menu_search_toolbar.xml\" value=\"0.275\" />\n        <entry key=\"..\\:/Users/wrzg8/AndroidStudioProjects/Hanime1/yenaly_libs/src/main/res/layout/yenaly_activity_crash_dialog.xml\" value=\"0.1\" />\n        <entry key=\"..\\:/Users/wrzg8/AppData/Local/Android/Sdk/platforms/android-32/data/res/layout/auto_complete_list.xml\" value=\"0.10389055151774262\" />\n        <entry key=\"..\\:/Users/wrzg8/AppData/Local/Android/Sdk/platforms/android-32/data/res/layout/simple_list_item_1.xml\" value=\"0.1265625\" />\n        <entry key=\"..\\:/Users/wrzg8/AppData/Local/Android/Sdk/platforms/android-32/data/res/layout/simple_list_item_2.xml\" value=\"0.1265625\" />\n      </map>\n    </option>\n  </component>\n  <component name=\"ExternalStorageConfigurationManager\" enabled=\"true\" />\n  <component name=\"ProjectRootManager\" version=\"2\" languageLevel=\"JDK_21\" default=\"true\" project-jdk-name=\"jbr-21\" project-jdk-type=\"JavaSDK\">\n    <output url=\"file://$PROJECT_DIR$/build/classes\" />\n  </component>\n  <component name=\"ProjectType\">\n    <option name=\"id\" value=\"Android\" />\n  </component>\n</project>"
  },
  {
    "path": ".idea/render.experimental.xml",
    "content": "<?xml version=\"1.0\" encoding=\"UTF-8\"?>\n<project version=\"4\">\n  <component name=\"RenderSettings\">\n    <option name=\"useLiveRendering\" value=\"false\" />\n  </component>\n</project>"
  },
  {
    "path": ".idea/runConfigurations.xml",
    "content": "<?xml version=\"1.0\" encoding=\"UTF-8\"?>\n<project version=\"4\">\n  <component name=\"RunConfigurationProducerService\">\n    <option name=\"ignoredProducers\">\n      <set>\n        <option value=\"com.intellij.execution.junit.AbstractAllInDirectoryConfigurationProducer\" />\n        <option value=\"com.intellij.execution.junit.AllInPackageConfigurationProducer\" />\n        <option value=\"com.intellij.execution.junit.PatternConfigurationProducer\" />\n        <option value=\"com.intellij.execution.junit.TestInClassConfigurationProducer\" />\n        <option value=\"com.intellij.execution.junit.UniqueIdConfigurationProducer\" />\n        <option value=\"com.intellij.execution.junit.testDiscovery.JUnitTestDiscoveryConfigurationProducer\" />\n        <option value=\"org.jetbrains.kotlin.idea.junit.KotlinJUnitRunConfigurationProducer\" />\n        <option value=\"org.jetbrains.kotlin.idea.junit.KotlinPatternConfigurationProducer\" />\n      </set>\n    </option>\n  </component>\n</project>"
  },
  {
    "path": ".idea/studiobot.xml",
    "content": "<?xml version=\"1.0\" encoding=\"UTF-8\"?>\n<project version=\"4\">\n  <component name=\"StudioBotProjectSettings\">\n    <option name=\"shareContext\" value=\"OptedIn\" />\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>\n  </component>\n  <component name=\"VcsDirectoryMappings\">\n    <mapping directory=\"$PROJECT_DIR$\" vcs=\"Git\" />\n  </component>\n</project>"
  },
  {
    "path": "LICENSE",
    "content": "                                 Apache License\n                           Version 2.0, January 2004\n                        http://www.apache.org/licenses/\n\n   TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION\n\n   1. Definitions.\n\n      \"License\" shall mean the terms and conditions for use, reproduction,\n      and distribution as defined by Sections 1 through 9 of this document.\n\n      \"Licensor\" shall mean the copyright owner or entity authorized by\n      the copyright owner that is granting the License.\n\n      \"Legal Entity\" shall mean the union of the acting entity and all\n      other entities that control, are controlled by, or are under common\n      control with that entity. For the purposes of this definition,\n      \"control\" means (i) the power, direct or indirect, to cause the\n      direction or management of such entity, whether by contract or\n      otherwise, or (ii) ownership of fifty percent (50%) or more of the\n      outstanding shares, or (iii) beneficial ownership of such entity.\n\n      \"You\" (or \"Your\") shall mean an individual or Legal Entity\n      exercising permissions granted by this License.\n\n      \"Source\" form shall mean the preferred form for making modifications,\n      including but not limited to software source code, documentation\n      source, and configuration files.\n\n      \"Object\" form shall mean any form resulting from mechanical\n      transformation or translation of a Source form, including but\n      not limited to compiled object code, generated documentation,\n      and conversions to other media types.\n\n      \"Work\" shall mean the work of authorship, whether in Source or\n      Object form, made available under the License, as indicated by a\n      copyright notice that is included in or attached to the work\n      (an example is provided in the Appendix below).\n\n      \"Derivative Works\" shall mean any work, whether in Source or Object\n      form, that is based on (or derived from) the Work and for which the\n      editorial revisions, annotations, elaborations, or other modifications\n      represent, as a whole, an original work of authorship. For the purposes\n      of this License, Derivative Works shall not include works that remain\n      separable from, or merely link (or bind by name) to the interfaces of,\n      the Work and Derivative Works thereof.\n\n      \"Contribution\" shall mean any work of authorship, including\n      the original version of the Work and any modifications or additions\n      to that Work or Derivative Works thereof, that is intentionally\n      submitted to Licensor for inclusion in the Work by the copyright owner\n      or by an individual or Legal Entity authorized to submit on behalf of\n      the copyright owner. For the purposes of this definition, \"submitted\"\n      means any form of electronic, verbal, or written communication sent\n      to the Licensor or its representatives, including but not limited to\n      communication on electronic mailing lists, source code control systems,\n      and issue tracking systems that are managed by, or on behalf of, the\n      Licensor for the purpose of discussing and improving the Work, but\n      excluding communication that is conspicuously marked or otherwise\n      designated in writing by the copyright owner as \"Not a Contribution.\"\n\n      \"Contributor\" shall mean Licensor and any individual or Legal Entity\n      on behalf of whom a Contribution has been received by Licensor and\n      subsequently incorporated within the Work.\n\n   2. Grant of Copyright License. Subject to the terms and conditions of\n      this License, each Contributor hereby grants to You a perpetual,\n      worldwide, non-exclusive, no-charge, royalty-free, irrevocable\n      copyright license to reproduce, prepare Derivative Works of,\n      publicly display, publicly perform, sublicense, and distribute the\n      Work and such Derivative Works in Source or Object form.\n\n   3. Grant of Patent License. Subject to the terms and conditions of\n      this License, each Contributor hereby grants to You a perpetual,\n      worldwide, non-exclusive, no-charge, royalty-free, irrevocable\n      (except as stated in this section) patent license to make, have made,\n      use, offer to sell, sell, import, and otherwise transfer the Work,\n      where such license applies only to those patent claims licensable\n      by such Contributor that are necessarily infringed by their\n      Contribution(s) alone or by combination of their Contribution(s)\n      with the Work to which such Contribution(s) was submitted. If You\n      institute patent litigation against any entity (including a\n      cross-claim or counterclaim in a lawsuit) alleging that the Work\n      or a Contribution incorporated within the Work constitutes direct\n      or contributory patent infringement, then any patent licenses\n      granted to You under this License for that Work shall terminate\n      as of the date such litigation is filed.\n\n   4. Redistribution. You may reproduce and distribute copies of the\n      Work or Derivative Works thereof in any medium, with or without\n      modifications, and in Source or Object form, provided that You\n      meet the following conditions:\n\n      (a) You must give any other recipients of the Work or\n          Derivative Works a copy of this License; and\n\n      (b) You must cause any modified files to carry prominent notices\n          stating that You changed the files; and\n\n      (c) You must retain, in the Source form of any Derivative Works\n          that You distribute, all copyright, patent, trademark, and\n          attribution notices from the Source form of the Work,\n          excluding those notices that do not pertain to any part of\n          the Derivative Works; and\n\n      (d) If the Work includes a \"NOTICE\" text file as part of its\n          distribution, then any Derivative Works that You distribute must\n          include a readable copy of the attribution notices contained\n          within such NOTICE file, excluding those notices that do not\n          pertain to any part of the Derivative Works, in at least one\n          of the following places: within a NOTICE text file distributed\n          as part of the Derivative Works; within the Source form or\n          documentation, if provided along with the Derivative Works; or,\n          within a display generated by the Derivative Works, if and\n          wherever such third-party notices normally appear. The contents\n          of the NOTICE file are for informational purposes only and\n          do not modify the License. You may add Your own attribution\n          notices within Derivative Works that You distribute, alongside\n          or as an addendum to the NOTICE text from the Work, provided\n          that such additional attribution notices cannot be construed\n          as modifying the License.\n\n      You may add Your own copyright statement to Your modifications and\n      may provide additional or different license terms and conditions\n      for use, reproduction, or distribution of Your modifications, or\n      for any such Derivative Works as a whole, provided Your use,\n      reproduction, and distribution of the Work otherwise complies with\n      the conditions stated in this License.\n\n   5. Submission of Contributions. Unless You explicitly state otherwise,\n      any Contribution intentionally submitted for inclusion in the Work\n      by You to the Licensor shall be under the terms and conditions of\n      this License, without any additional terms or conditions.\n      Notwithstanding the above, nothing herein shall supersede or modify\n      the terms of any separate license agreement you may have executed\n      with Licensor regarding such Contributions.\n\n   6. Trademarks. This License does not grant permission to use the trade\n      names, trademarks, service marks, or product names of the Licensor,\n      except as required for reasonable and customary use in describing the\n      origin of the Work and reproducing the content of the NOTICE file.\n\n   7. Disclaimer of Warranty. Unless required by applicable law or\n      agreed to in writing, Licensor provides the Work (and each\n      Contributor provides its Contributions) on an \"AS IS\" BASIS,\n      WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or\n      implied, including, without limitation, any warranties or conditions\n      of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A\n      PARTICULAR PURPOSE. You are solely responsible for determining the\n      appropriateness of using or redistributing the Work and assume any\n      risks associated with Your exercise of permissions under this License.\n\n   8. Limitation of Liability. In no event and under no legal theory,\n      whether in tort (including negligence), contract, or otherwise,\n      unless required by applicable law (such as deliberate and grossly\n      negligent acts) or agreed to in writing, shall any Contributor be\n      liable to You for damages, including any direct, indirect, special,\n      incidental, or consequential damages of any character arising as a\n      result of this License or out of the use or inability to use the\n      Work (including but not limited to damages for loss of goodwill,\n      work stoppage, computer failure or malfunction, or any and all\n      other commercial damages or losses), even if such Contributor\n      has been advised of the possibility of such damages.\n\n   9. Accepting Warranty or Additional Liability. While redistributing\n      the Work or Derivative Works thereof, You may choose to offer,\n      and charge a fee for, acceptance of support, warranty, indemnity,\n      or other liability obligations and/or rights consistent with this\n      License. However, in accepting such obligations, You may act only\n      on Your own behalf and on Your sole responsibility, not on behalf\n      of any other Contributor, and only if You agree to indemnify,\n      defend, and hold each Contributor harmless for any liability\n      incurred by, or claims asserted against, such Contributor by reason\n      of your accepting any such warranty or additional liability.\n\n   END OF TERMS AND CONDITIONS\n\n   Copyright 2021 Yenaly\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"
  },
  {
    "path": "README.md",
    "content": "# Han1meViewer\n\n![Han1meViewer](https://socialify.git.ci/YenalyLiew/Han1meViewer/image?description=1&font=KoHo&forks=1&issues=1&language=1&logo=https%3A%2F%2Fgithub.com%2FYenalyLiew%2FHan1meViewer%2Fblob%2Fmaster%2Ficon%2Ficon_han1me_viewer-rurires.png%3Fraw%3Dtrue&name=1&owner=1&pattern=Plus&pulls=1&stargazers=1&theme=Dark)\n\nHan1meViewer 是一个使用 Kotlin 开发的第三方动漫浏览器，旨在为用户提供流畅、美观且高效的动画内容浏览体验。支持多种自定义与扩展，适合二次元爱好者和开发者共同完善。\n\n## 功能特点\n\n- **Anime4K超分**：集成 Anime4K 技术，提供高清晰度的动画画质。\n- **动画内容浏览**：快速、流畅地浏览、搜索和筛选动漫资源。\n- **多主题支持**：支持深色与浅色主题，个性化你的观影体验。\n- **收藏与历史记录**：一键收藏喜欢的动画，随时查看观影历史。\n- **高性能**：基于 Kotlin，界面响应快，占用资源少。\n- **开源自由**：欢迎二次开发与贡献，持续优化功能。\n- **画中画**：支持画中画模式，边看边做其他事情。\n- **垂直视频适配**：优化垂直视频播放体验，适合手机用户。\n\n## 安装与运行\n\n1. 克隆本仓库到本地：\n   ```bash\n   git clone https://github.com/Night-stars-1/Han1meViewer.git\n   ```\n2. 使用 Android Studio 或其他支持 Kotlin 的 IDE 打开项目。\n3. 配置依赖并编译运行到你的 Android 设备。\n\n## 项目结构\n\n- `app/` 主程序源码\n- `res/` 资源文件（图片、布局等）\n- `gradle/` 构建相关配置\n- `README.md` 项目说明\n- 其他相关代码和配置文件\n\n## 贡献指南\n\n欢迎任何形式的贡献！你可以通过以下方式参与项目：\n\n- 提交 Issue 报告 bug 或建议新功能\n- Fork 并提交 Pull Request\n- 优化文档与翻译\n\n## 特别鸣谢\n\n- 感谢所有参与本项目的开发者和贡献者\n- [Han1meViewer(YenalyLiew)](https://github.com/YenalyLiew/Han1meViewer) 原项目\n- [Anime4K](https://github.com/bloc97/Anime4K) Anime4K超分辨率\n- [mpv-android](https://github.com/abdallahmehiz/mpv-android) MPV 播放器内核\n- [mpv-android](https://github.com/mpv-android/mpv-android) MPV 播放器内核\n\n## License\n\n本项目采用 Apache-2.0 开源许可证，详见 [LICENSE](LICENSE) 文件。"
  },
  {
    "path": "README_TECH.md",
    "content": "# Han1meViewer 技术相关\n\n> 抄是程序员进步的阶梯。\n\n## 概括\n\n本软件使用 MVVM 架构，Material 3 视觉风格，Jetpack 不用问肯定用，但未使用 Compose（有一说一不用 Compose\n写 xml 真是写到吐）。网络请求使用 Retrofit，图片加载使用 Coil，视频播放使用 Jiaozi，Json 解析使用\nSerialization，部分弹窗使用的 Xpopup。未使用 LiveData，全部改用功能更强大的 Flow。\n\n## 受众人群\n\n这篇文章主要给谁看的呢？一是那些刚学习 Android 的同学，想看看本项目是怎么写的，或者对其中某个功能很感兴趣，想学习一下并且快速集成于自己的\nApp 中；二是普通开发者感兴趣来捧个场，能学到东西更好，写的不对的来发 discussion 拷打我。\n\n## 功能解析\n\n### 断点续传下载\n\n#### 你可以学到\n\n1. WorkManager 使用，如何在 WorkManager 中对下载任务进行基础管理？\n2. RecyclerView 使用，DiffUtil 使用，如何充分利用 `payload` 参数对某个特定的控件进行刷新？\n3. Room 使用，如何通过数据库实现回调？\n\n#### 关键文件\n\n- [HanimeDownloadWorker.kt](app/src/main/java/com/yenaly/han1meviewer/worker/HanimeDownloadWorker.kt) - 关键作业类\n- [HanimeDownloadEntity.kt](app/src/main/java/com/yenaly/han1meviewer/logic/entity/HanimeDownloadEntity.kt) - 下载 实体类\n- [HanimeDownloadDao.kt](app/src/main/java/com/yenaly/han1meviewer/logic/dao/HanimeDownloadDao.kt) - 下载 Dao 类\n- [DownloadDatabase.kt](app/src/main/java/com/yenaly/han1meviewer/logic/dao/DownloadDatabase.kt) - 下载 数据库类\n- [HanimeDownloadingRvAdapter.kt](app/src/main/java/com/yenaly/han1meviewer/ui/adapter/HanimeDownloadingRvAdapter.kt) - 下载界面的 RecyclerView Adapter\n\n#### 解释\n\n你可能问我你就这几个文件就实现了？我接口呢，没接口你怎么回调的？\n\n**先去看**我写的 [小白如何快速实现简单的可保存状态断点续传后台下载？一个 Jetpack 库搞定一切！](https://juejin.cn/post/7278929337067225149)，看完再看下面。\n\n但是不要照搬，使用前要注意这么几点：\n\n1. 你所下载的东西是否可以断点续传？对于视频类 App 来说，视频基本都是可以断点续传的，毕竟要播放嘛！所以我在实现下载的时候不必考虑那么多。\n2. 是否要对每个下载任务进行很粒度的操作？不是说不行，但可能实现起来有点麻烦。\n3. 一次性下载数目是否很多？如果使用上述文章的做法去下载极多文件可能会对手机性能造成一定压力，一会细说。\n\n为什么说下载数目过多会造成一定压力？\n\n聚焦于 [HanimeDownloadWorker.kt](app/src/main/java/com/yenaly/han1meviewer/worker/HanimeDownloadWorker.kt) 第 180 行左右：\n\n```kotlin\nconst val RESPONSE_INTERVAL = 500L\n\nif (System.currentTimeMillis() - delayTime > RESPONSE_INTERVAL) {\n    val progress = entity.downloadedLength * 100 / entity.length\n    setProgress(workDataOf(PROGRESS to progress.toInt()))\n    setForeground(createForegroundInfo(progress.toInt()))\n    DatabaseRepo.HanimeDownload.update(entity)\n    delayTime = System.currentTimeMillis()\n}\n```\n\n我在 App 里设置的是 500 ms 一更新，相当于 `2 次数据库更新操作/s/job`，加上通过 Flow/LiveData 回调，当数据库检测到数据更新，会立即返回全新的、拥有最新数据的列表，相当于又有 `回调 2 次/s/job`。如果一次性下载极多个文件，并且调低了 `RESPONSE_INTERVAL`，可能会对数据库造成一定负担。这个时候这种方法就不太好用了。\n\n配置好了 RecyclerView，那刷新闪烁问题该如何解决？我在原文章中提供的方法并不好：\n\n```kotlin\nrv.itemAnimator?.changeDuration = 0\n```\n\n这句代码只是解决了表面问题，实际上背后还是接着“闪”。因为即使是通过了 DiffUtil 进行了差分刷新，但还仍是全局更新，这只是自我欺骗罢了。不信你可以试试 `holder.binding.pbProgress.setProgress(item.progress, true)` 能不能正常出现动态效果。那怎么实现，`isDownloading` 字段发生修改，就单独对暂停按钮修改；`downloadedLength` 字段发生修改，就单独对进度条修改？这时候就需要 `payload` 出场了。\n\n与 `payload` 相关的文章真的挺多，StackOverflow 甚至 掘金 上不少介绍这个的文章，自己去搜一搜马上就能看懂，我就不赘述了。关键就是 `DiffUtil.ItemCallback` 中的 `getChangePayload` 方法和 `onBindViewHolder` 中的 `payloads` 参数。\n\n**先去看** `payload` 使用相关文章，再看下面。\n\n但我发现，很多人确实介绍了这种方法，但鲜少有人去介绍如何高效率实现一次性去处理多个字段。你可能想到了 `List<Int>` 或 `IntArray`，通过遍历对应去处理每一种情况。这样的话，时间复杂度和空间复杂度都是 `O(n)`，`n` 是你需要监听的数目；再聪明点也可以想到使用 `Set<Int>`，在 `onBindViewHolder` 中分别查询 set 中是否含有某个情况来对应处理，这时候时间复杂度降到了 `O(1)`。如果在刷新不频繁的情况下，这样做确实没什么不妥，但是高强度下，每次 new 一个数据结构确实是一个小负担，那应该怎么样做呢？\n\n这时候可以选择简单的 Bitmap 数据结构。你可能刚听说，但它确实很常见，你在使用 `Intent#addFlags` 打开新 Activity 的时候，大概率会接触到这种数据结构。我们可以利用一个仅 4 个字节的 32-bit 整数值去实现查找 (`find`)、判空 (`isEmpty`)、添加 (`add`) 的功能（我们只需要这些功能，而且不同情况数量大概率不超过 32 个）。\n\n聚焦于 [HanimeDownloadingRvAdapter.kt](app/src/main/java/com/yenaly/han1meviewer/ui/adapter/HanimeDownloadingRvAdapter.kt)\n\n> 注意：我使用了 BRVAH 作为 RecyclerView 的代替，所以具体方法和 RecyclerView 不一定一致，但使用方法基本一致。\n\n```kotlin\ncompanion object {\n    private const val DOWNLOADING = 1 // 0000 0001\n    private const val PAUSE = 1 shl 1 // 0000 0010\n\n    val COMPARATOR = object : DiffUtil.ItemCallback<HanimeDownloadEntity>() {\n        override fun areContentsTheSame(\n            oldItem: HanimeDownloadEntity,\n            newItem: HanimeDownloadEntity,\n        ): Boolean {\n            return oldItem == newItem\n        }\n\n        override fun areItemsTheSame(\n            oldItem: HanimeDownloadEntity,\n            newItem: HanimeDownloadEntity,\n        ): Boolean {\n            return oldItem.id == newItem.id\n        }\n\n        override fun getChangePayload(\n            oldItem: HanimeDownloadEntity,\n            newItem: HanimeDownloadEntity,\n        ): Any {\n            // 假设当前只有 progress 和原来不一样\n            var bitset = 0\n            // bitset == 0000 0000\n            if (oldItem.progress != newItem.progress || oldItem.downloadedLength != newItem.downloadedLength)\n                bitset = bitset or DOWNLOADING\n            \t// bitset == 0000 0001\n            if (oldItem.isDownloading != newItem.isDownloading)\n                bitset = bitset or PAUSE\n            \t// 不经过这里\n            return bitset\n            // return 0000 0001\n        }\n    }\n}\n```\n\n```kotlin\noverride fun onBindViewHolder(\n    holder: DataBindingHolder<ItemHanimeDownloadingBinding>,\n    position: Int,\n    item: HanimeDownloadEntity?,\n    payloads: List<Any>,\n) {\n    // 如果 payloads 列表为空，或者为 0000 0000，说明不需要修改\n    if (payloads.isEmpty() || payloads.first() == 0)\n        return super.onBindViewHolder(holder, position, item, payloads)\n    item.notNull()\n    val bitset = payloads.first() as Int\n    // 0000 0001 & 0000 0001 = 0000 0001 != 0000 0000\n    // 对进度相关控件进行修改\n    if (bitset and DOWNLOADING != 0) {\n        holder.binding.tvSize.text = spannable {\n            item.downloadedLength.formatFileSize().text()\n            \" | \".span { color(Color.RED) }\n            item.length.formatFileSize().span { style(Typeface.BOLD) }\n        }\n        holder.binding.tvProgress.text = \"${item.progress}%\"\n        holder.binding.pbProgress.setProgress(item.progress, true)\n    }\n    // 0000 0001 & 0000 0010 = 0000 0000 == 0000 0000\n    // 不经过下面\n    if (bitset and PAUSE != 0) {\n        holder.binding.btnStart.handleStartButton(item.isDownloading)\n    }\n}\n```\n\n就这样实现了效率比较高的差分刷新。\n\n### CI 更新渠道\n\n#### 你可以学到\n\n#### 关键文件\n\n#### 解释\n\n当你的软件拓展性比较高，但受限于题材内容或者单纯懒，不方便自建服务器去读取这些拓展文件。但你又希望能让用户通过其他渠道实时的获取到更新（比如好心人上传了拓展文件，我合并到主分支之后，几分钟后用户就可以获得更新，而不用我自己做包），但又不是所有人需要这些拓展功能（要是人家不愿用你那功能，又一会一个 Release，用户也会烦；你自己一会发一个包你也会烦）。所以能不能给用户提供两种渠道？一个是稳定更新渠道，自己发版本；另一个是开发版，GitHub 自动构建，保证最新功能（最新拓展功能立即集成）但不保证稳定性。\n\n答案是肯定的。其实我之前也不知道怎么做，但是 @NekoOuO 给我发了 [Foolbar/EhViewer](https://github.com/FooIbar/EhViewer/) 的做法，我想都没想就抄过来了。但没人详细教怎么做，我今天就来讲讲。\n\n**先去看** GitHub CI 基础用法。\n\n谷歌、掘金上全是教程。你先去查一查用法然后配置一下，刚开始的要求不多，你上传 commit 之后，GitHub CI 开始工作并成功 Build，就算入门了，先不用管 Build 之后干什么或者别的。如果你操作非常顺利，再看以下步骤。\n\n待更...\n\n### 共享关键H帧\n\n#### 你可以学到\n\n1. 如何充分利用 Kotlin 的集合操作函数，将一个个单独的 JSON 文件进行排序、分类甚至扁平化？\n\n   相关函数：`groupBy`、`flatMap`、`sortedWith` `=>` `compareBy`、`thenBy`\n\n#### 关键文件\n\n- [HKeyframes 文件夹](app/src/main/assets/h_keyframes) - 存放所有共享关键H帧\n- [DatabaseRepo.kt](app/src/main/java/com/yenaly/han1meviewer/logic/DatabaseRepo.kt) - 处理共享关键H帧\n- [SharedHKeyframesRvAdapter.kt](app/src/main/java/com/yenaly/han1meviewer/ui/adapter/SharedHKeyframesRvAdapter.kt) - 界面 Adapter\n- [HKeyframeEntity.kt](app/src/main/java/com/yenaly/han1meviewer/logic/entity/HKeyframeEntity.kt) - 相关实体类\n\n#### 解释\n\n很多人看到 [HKeyframes 文件夹](app/src/main/assets/h_keyframes) 先笑了，所有 JSON 文件都放一块，作者是个傻宝吧，这都不知道分文件夹来分类？\n\n你以为我没想到吗？首先分文件夹为什么不太行：\n\n1. 分文件夹无法一次性读取到对应影片的关键H帧。比如你正在看 `videoCode` 为 `114514` 的影片，我不分文件夹直接读取文件夹下的对应文件即可，不需要遍历各个文件夹去寻找，相当于 List 和 Map 的区别。\n2. 假设分文件夹后，在根目录创建 JSON 来写好哪个文件夹包含哪些影片的代号，也不是不行，但是会增加其他想提供共享H帧的人的负担。\n\n主要还是历史遗留问题，我懒得改了😄。Kotlin 这么多集合操作函数，分个组排个序不轻轻松松？\n\n我现在给你一个关键H帧的 JSON，你来考虑考虑怎么转化为以下格式：\n\n格式：\n\n```\n- 系列 1\n\t- 系列 1 第一集\n\t- 系列 1 第二集\n\t- 系列 1 第三集\n- 系列 2\n\t- 系列 2 第一集\n\t- 系列 2 第二集\n```\n\n随机一段关键H帧：\n\n> 你要注意，该网站的 `videoCode` 不是按照顺序排列的，第一集和第二集中间可能会夹带一个其他系列的影片。\n\n```json\n{\n  \"videoCode\": \"114514\",\n  \"group\": \"系列 2\",\n  \"title\": \"系列 2 第二集\",\n  \"episode\": 2,\n  \"author\": \"Bekki Chen\",\n  \"keyframes\": [\n    {\n      \"position\": 482500,\n      \"prompt\": null\n    },\n    {\n      \"position\": 500500,\n      \"prompt\": null\n    },\n    {\n      \"position\": 556000,\n      \"prompt\": null\n    },\n    {\n      \"position\": 777300,\n      \"prompt\": null\n    }\n  ]\n}\n```\n\n你可能想用 Map 分类，但是 RecyclerView 可是传不了 Map 的，那怎么才能扁平化成一个 List，并且能实现  RecyclerView 多布局呢？如果是两种截然不同的两个数据去实现 RecyclerView 多布局，不得不依靠接口，比如说本 App 中共享关键H帧界面中数据不一样的标题和内容。\n\n聚焦于 [HKeyframeEntity.kt](app/src/main/java/com/yenaly/han1meviewer/logic/entity/HKeyframeEntity.kt)\n\n```kotlin\ninterface MultiItemEntity {\n    val itemType: Int\n}\n\ninterface HKeyframeType : MultiItemEntity {\n    companion object {\n        const val H_KEYFRAME = 0\n        const val HEADER = 1\n    }\n}\n```\n\n然后 HKeyframeEntity 和 HKeyframeHeader 我就不多说了，把正确的 `itemType` override 给对应的 `itemType` 字段就好。\n\n现在问题是怎么读取那些共享关键H帧并将其扁平化？\n\n聚焦于 [DatabaseRepo.kt](app/src/main/java/com/yenaly/han1meviewer/logic/DatabaseRepo.kt)\n\n```kotlin\n@OptIn(ExperimentalSerializationApi::class)\nfun loadAllShared(): Flow<List<HKeyframeType>> = flow {\n    val res = applicationContext.assets.let { assets ->\n        // assets.list 方法获取到文件夹所有文件的 List\n        assets.list(\"h_keyframes\")?.asSequence() // 将其转化为一个序列\n            ?.filter { it.endsWith(\".json\") } // 把其中结尾为 json 的挑出来\n            ?.mapNotNull { fileName -> // 将 文件名 映射 为 文件，再通过 文件 转化为 实体\n                try {\n                    // assets.open 方法打开文件\n                    assets.open(\"h_keyframes/$fileName\").use { inputStream ->\n                        Json.decodeFromStream<HKeyframeEntity>(inputStream)\n                    }\n                } catch (e: Exception) { // 出现问题返回 null\n                    e.printStackTrace()\n                    null\n                }\n            }\n            ?.sortedWith(\n                compareBy<HKeyframeEntity> { it.group }.thenBy { it.episode }\n            ) // 排序，先以 group 进行排序，然后对 episode 进行排序\n            ?.groupBy { it.group ?: \"???\" } // 分组，以 group 为 key，以 group 下的所有影片的列表为 value 建立 Map，若 group 为 null，加入组 ??? 里\n            ?.flatMap { (group, entities) -> // 提供两个参数，分别为 key 和 value\n                listOf(HKeyframeHeader(title = group, attached = entities)) + entities\n            } // 关键：扁平化，group 与 entities 由主从关系变为并列关系\n            .orEmpty() // 若 list 为 null，返回一个长度为 0 的空列表\n    }\n    emit(res)\n}\n```\n\n然后在对应 RecyclerView 中设置好 `itemType`，再分 `itemType` 配置相关函数就可以了。\n\n具体查看 [SharedHKeyframesRvAdapter.kt](app/src/main/java/com/yenaly/han1meviewer/ui/adapter/SharedHKeyframesRvAdapter.kt) \n"
  },
  {
    "path": "app/.gitignore",
    "content": "/build\n/keystore/ha1_keystore_password.txt\n/ha1_github_token.txt\n"
  },
  {
    "path": "app/build.gradle.kts",
    "content": "@file:Suppress(\"UnstableApiUsage\")\n\nimport Config.Version.createVersion\nimport Config.Version.source\nimport Config.isRelease\nimport Config.lastCommitSha\nimport com.android.build.gradle.internal.api.BaseVariantOutputImpl\n\nplugins {\n    alias(libs.plugins.com.android.application)\n    alias(libs.plugins.org.jetbrains.kotlin.android)\n    alias(libs.plugins.org.jetbrains.kotlin.plugin.parcelize)\n    alias(libs.plugins.org.jetbrains.kotlin.plugin.serialization)\n    alias(libs.plugins.com.google.devtools.ksp)\n    alias(libs.plugins.com.google.gms.google.services)\n    alias(libs.plugins.com.google.firebase.crashlytics)\n    alias(libs.plugins.com.google.firebase.firebase.pref)\n    // alias(libs.plugins.compose.compiler)\n}\n\nandroid {\n    compileSdk = property(\"compile.sdk\")?.toString()?.toIntOrNull()\n\n    val commitSha = if (isRelease) lastCommitSha else \"b8eace8\" // 方便调试\n\n    // 先 Github Secrets 再读取环境变量，若没有则读取本地文件\n    val signPwd = System.getenv(\"HA1_KEYSTORE_PASSWORD\") ?: File(\n        projectDir, \"keystore/ha1_keystore_password.txt\"\n    ).checkIfExists()?.readText().orEmpty()\n\n    val githubToken = System.getenv(\"HA1_GITHUB_TOKEN\") ?: File(\n        projectDir, \"ha1_github_token.txt\"\n    ).checkIfExists()?.readText().orEmpty()\n\n    val signConfig = if (isRelease) signingConfigs.create(\"release\") {\n        storeFile = File(projectDir, \"keystore/Han1meViewerKeystore.jks\").checkIfExists()\n        storePassword = signPwd\n        keyAlias = \"night_star\"\n        keyPassword = signPwd\n        enableV3Signing = true\n        enableV4Signing = true\n    } else null\n\n    defaultConfig {\n        applicationId = \"com.yenaly.han1meviewer\"\n        minSdk = property(\"min.sdk\")?.toString()?.toIntOrNull()\n        targetSdk = property(\"target.sdk\")?.toString()?.toIntOrNull()\n        val (code, name) = createVersion(major = 0, minor = 18, patch = 0)\n        versionCode = code\n        versionName = name\n\n        testInstrumentationRunner = \"androidx.test.runner.AndroidJUnitRunner\"\n\n        buildConfigField(\"String\", \"COMMIT_SHA\", \"\\\"$commitSha\\\"\")\n        buildConfigField(\"String\", \"VERSION_NAME\", \"\\\"${versionName}\\\"\")\n        buildConfigField(\"int\", \"VERSION_CODE\", \"$versionCode\")\n        buildConfigField(\"String\", \"HA1_GITHUB_TOKEN\", \"\\\"${githubToken}\\\"\")\n        buildConfigField(\"String\", \"HA1_VERSION_SOURCE\", \"\\\"${source}\\\"\")\n    }\n\n    splits {\n        abi {\n            isEnable = true\n            reset()\n            include(\"armeabi-v7a\", \"arm64-v8a\")\n            isUniversalApk = true\n        }\n    }\n\n    buildTypes {\n        release {\n            isMinifyEnabled = true\n            signingConfig = signConfig\n            proguardFiles(\n                getDefaultProguardFile(\"proguard-android-optimize.txt\"), \"proguard-rules.pro\"\n            )\n        }\n        debug {\n            isMinifyEnabled = false\n            proguardFiles(\n                getDefaultProguardFile(\"proguard-android-optimize.txt\"), \"proguard-rules.pro\"\n            )\n            applicationIdSuffix = \".debug\"\n        }\n    }\n    applicationVariants.all {\n        outputs.forEach { output ->\n            val outputImpl = output as BaseVariantOutputImpl\n            val abi = outputImpl.filters.find { it.filterType == \"ABI\" }?.identifier\n\n            outputImpl.outputFileName = if (abi != null) {\n                \"Han1meViewer-v${defaultConfig.versionName}_$abi.apk\"\n            } else {\n                \"Han1meViewer-v${defaultConfig.versionName}_universal.apk\"\n            }\n        }\n    }\n    buildFeatures {\n        //noinspection DataBindingWithoutKapt\n        dataBinding = true\n        buildConfig = true\n        // compose = true\n    }\n    compileOptions {\n        isCoreLibraryDesugaringEnabled = true\n        sourceCompatibility = JavaVersion.VERSION_21\n        targetCompatibility = JavaVersion.VERSION_21\n    }\n    kotlinOptions {\n        jvmTarget = JavaVersion.VERSION_21.toString()\n        freeCompilerArgs = listOf(\"-opt-in=kotlin.RequiresOptIn\", \"-Xjvm-default=all-compatibility\")\n    }\n    lint {\n        disable += setOf(\"EnsureInitializerMetadata\")\n    }\n    namespace = \"com.yenaly.han1meviewer\"\n}\n\ndependencies {\n\n    implementation(project(\":yenaly_libs\"))\n\n    // android related\n\n    implementation(libs.bundles.android.base)\n    implementation(libs.bundles.android.jetpack)\n    implementation(libs.palette)\n\n//    implementation(platform(libs.compose.compose.bom))\n//    androidTestImplementation(platform(libs.compose.compose.bom))\n//    implementation(libs.compose.material3)\n//    implementation(libs.androidx.activity.compose)\n//    implementation(libs.compose.ui.graphics)\n//    implementation(libs.compose.ui.ui.tooling.preview)\n//    debugImplementation(libs.compose.ui.ui.tooling)\n\n    // datetime\n\n    implementation(libs.datetime)\n\n    // parse\n\n    implementation(libs.serialization.json)\n    implementation(libs.jsoup)\n\n    // network\n\n    implementation(libs.retrofit)\n    implementation(libs.converter.serialization)\n\n    // pic\n\n    implementation(libs.coil)\n\n    // popup\n\n    implementation(libs.xpopup)\n    implementation(libs.xpopup.ext)\n\n    // video\n\n    implementation(libs.jiaozi.video.player)\n    implementation(libs.media3.exoplayer)\n    implementation(libs.media3.exoplayer.hls)\n\n    // view\n\n    implementation(libs.refresh.layout.kernel)\n    implementation(libs.refresh.header.material)\n    implementation(libs.refresh.footer.classics)\n    implementation(libs.multitype)\n    implementation(libs.base.recyclerview.adapter.helper4)\n    implementation(libs.expandable.textview)\n    implementation(libs.spannable.x)\n    implementation(libs.about)\n    implementation(libs.statelayout)\n    implementation(libs.circular.reveal.switch)\n\n    // firebase\n\n    implementation(platform(libs.firebase.bom))\n    implementation(libs.firebase.analytics)\n    implementation(libs.firebase.crashlytics)\n    implementation(libs.firebase.perf)\n    implementation(libs.firebase.config)\n\n    // mpv\n    implementation(libs.mpv.lib)\n\n    ksp(libs.room.compiler)\n\n    coreLibraryDesugaring(libs.desugar.jdk.libs)\n\n    testImplementation(libs.junit)\n\n    androidTestImplementation(libs.test.junit)\n    androidTestImplementation(libs.test.espresso.core)\n\n    // debugImplementation(libs.leak.canary)\n}\n\n/**\n * This function is used to check if a file exists and is a file.\n */\nfun File.checkIfExists(): File? = if (exists() && isFile) this else null"
  },
  {
    "path": "app/proguard-rules.pro",
    "content": "# Add project specific ProGuard rules here.\n# You can control the set of applied configuration files using the\n# proguardFiles setting in build.gradle.kts.\n#\n# For more details, see\n#   http://developer.android.com/guide/developing/tools/proguard.html\n\n# If your project uses WebView with JS, uncomment the following\n# and specify the fully qualified class name to the JavaScript interface\n# class:\n#-keepclassmembers class fqcn.of.javascript.interface.for.webview {\n#   public *;\n#}\n\n# Uncomment this to preserve the line number information for\n# debugging stack traces.\n#-keepattributes SourceFile,LineNumberTable\n\n# If you keep the line number information, uncomment this to\n# hide the original source file name.\n#-renamesourcefileattribute SourceFile\n\n-keepattributes SourceFile, LineNumberTable\n\n-keepnames class * extends android.app.Activity\n-keepnames class * extends androidx.fragment.app.Fragment\n\n-keep class * extends cn.jzvd.** { *; }\n\n-keep class com.google.android.gms.** { *; }\n-keep interface com.google.android.gms.** { *; }\n-keep,allowoptimization class is.xyz.mpv.** { public protected *; }"
  },
  {
    "path": "app/src/androidTest/java/com/yenaly/han1meviewer/ExampleInstrumentedTest.kt",
    "content": "package com.yenaly.han1meviewer\n\nimport androidx.test.platform.app.InstrumentationRegistry\nimport androidx.test.ext.junit.runners.AndroidJUnit4\n\nimport org.junit.Test\nimport org.junit.runner.RunWith\n\nimport org.junit.Assert.*\n\n/**\n * Instrumented test, which will execute on an Android device.\n *\n * See [testing documentation](http://d.android.com/tools/testing).\n */\n@RunWith(AndroidJUnit4::class)\nclass ExampleInstrumentedTest {\n    @Test\n    fun useAppContext() {\n        // Context of the app under test.\n        val appContext = InstrumentationRegistry.getInstrumentation().targetContext\n        assertEquals(\"com.yenaly.han1meviewer\", appContext.packageName)\n    }\n}"
  },
  {
    "path": "app/src/main/AndroidManifest.xml",
    "content": "<?xml version=\"1.0\" encoding=\"utf-8\"?>\n<manifest xmlns:android=\"http://schemas.android.com/apk/res/android\"\n    xmlns:tools=\"http://schemas.android.com/tools\">\n\n    <uses-permission android:name=\"android.permission.INTERNET\" />\n    <uses-permission android:name=\"android.permission.POST_NOTIFICATIONS\" />\n    <uses-permission android:name=\"android.permission.FOREGROUND_SERVICE_DATA_SYNC\" />\n    <uses-permission android:name=\"android.permission.REQUEST_INSTALL_PACKAGES\" />\n    <uses-permission android:name=\"android.permission.MANAGE_EXTERNAL_STORAGE\"/>\n\n    <queries>\n        <intent>\n            <action android:name=\"android.intent.action.VIEW\" />\n            <data android:mimeType=\"video/*\" />\n        </intent>\n    </queries>\n\n    <!-- 暂时关闭了 enableOnBackInvokedCallback，因为稳定性还是有点差 -->\n    <application\n        android:name=\".HanimeApplication\"\n        android:allowBackup=\"true\"\n        android:dataExtractionRules=\"@xml/data_extraction_rules\"\n        android:enableOnBackInvokedCallback=\"false\"\n        android:fullBackupContent=\"@xml/backup_rules\"\n        android:icon=\"@mipmap/ic_launcher\"\n        android:label=\"@string/hanime_app_name\"\n        android:roundIcon=\"@mipmap/ic_launcher_round\"\n        android:supportsRtl=\"true\"\n        android:theme=\"@style/Theme.Hanime1\"\n        tools:targetApi=\"34\">\n\n        <service\n            android:name=\"androidx.work.impl.foreground.SystemForegroundService\"\n            android:foregroundServiceType=\"dataSync\"\n            tools:node=\"merge\" />\n\n        <provider\n            android:name=\"androidx.startup.InitializationProvider\"\n            android:authorities=\"${applicationId}.androidx-startup\"\n            android:exported=\"false\"\n            android:initOrder=\"114514\"\n            tools:node=\"merge\">\n            <meta-data\n                android:name=\"com.yenaly.han1meviewer.HInitializer\"\n                android:value=\"androidx.startup\" />\n        </provider>\n\n        <provider\n            android:name=\"androidx.core.content.FileProvider\"\n            android:authorities=\"${applicationId}.fileProvider\"\n            android:exported=\"false\"\n            android:grantUriPermissions=\"true\">\n\n            <meta-data\n                android:name=\"android.support.FILE_PROVIDER_PATHS\"\n                android:resource=\"@xml/file_paths\" />\n\n        </provider>\n\n        <activity\n            android:name=\".ui.activity.VideoActivity\"\n            android:supportsPictureInPicture=\"true\"\n            android:configChanges=\"keyboard|keyboardHidden|orientation|screenSize|screenLayout|smallestScreenSize|uiMode\"\n            android:exported=\"true\"\n            android:screenOrientation=\"portrait\"\n            tools:ignore=\"LockedOrientationActivity\">\n            <intent-filter android:autoVerify=\"true\">\n                <action android:name=\"android.intent.action.VIEW\" />\n\n                <category android:name=\"android.intent.category.DEFAULT\" />\n                <category android:name=\"android.intent.category.BROWSABLE\" />\n\n                <data android:scheme=\"http\" />\n                <data android:scheme=\"https\" />\n                <data android:host=\"hanime1.com\" />\n                <data android:host=\"hanime1.me\" />\n                <data android:path=\"/watch\" />\n            </intent-filter>\n        </activity>\n        <activity\n            android:name=\".ui.activity.MainActivity\"\n            android:configChanges=\"orientation|screenSize\"\n            android:exported=\"true\"\n            android:launchMode=\"singleTask\"\n            android:theme=\"@style/Theme.Hanime1.Main\">\n            <intent-filter>\n                <action android:name=\"android.intent.action.MAIN\" />\n\n                <category android:name=\"android.intent.category.LAUNCHER\" />\n            </intent-filter>\n        </activity>\n        <activity\n            android:name=\".ui.activity.SearchActivity\"\n            android:configChanges=\"orientation|screenSize\"\n            android:exported=\"true\">\n\n        </activity>\n        <activity\n            android:name=\".ui.activity.PreviewActivity\"\n            android:configChanges=\"screenSize|orientation\"\n            android:exported=\"true\"\n            android:screenOrientation=\"portrait\"\n            android:theme=\"@style/Theme.Hanime1.Preview\"\n            tools:ignore=\"LockedOrientationActivity\">\n\n        </activity>\n        <activity\n            android:name=\".ui.activity.PreviewCommentActivity\"\n            android:configChanges=\"screenSize|orientation\"\n            android:exported=\"true\">\n\n        </activity>\n        <activity\n            android:name=\".ui.activity.SettingsActivity\"\n            android:configChanges=\"screenSize|orientation\"\n            android:exported=\"true\">\n\n        </activity>\n        <activity\n            android:name=\".ui.activity.LoginActivity\"\n            android:configChanges=\"screenSize|orientation\"\n            android:exported=\"true\">\n\n        </activity>\n        <activity\n            android:name=\".ui.activity.DownloadActivity\"\n            android:configChanges=\"orientation|screenSize\"\n            android:exported=\"true\"></activity>\n    </application>\n</manifest>"
  },
  {
    "path": "app/src/main/assets/h_keyframes/12444.json",
    "content": "{\n  \"videoCode\": \"12444\",\n  \"group\": \"思春期SEX\",\n  \"title\": \"思春期SEX#1\",\n  \"episode\": 1,\n  \"author\": \"NeKoOuO\",\n  \"keyframes\": [\n    {\n      \"position\": 482500,\n      \"prompt\": null\n    },\n    {\n      \"position\": 545000,\n      \"prompt\": null\n    },\n    {\n      \"position\": 832000,\n      \"prompt\": null\n    }\n  ]\n}"
  },
  {
    "path": "app/src/main/assets/h_keyframes/14463.json",
    "content": "{\n  \"videoCode\": \"14463\",\n  \"group\": \"思春期SEX\",\n  \"title\": \"思春期SEX#3\",\n  \"episode\": 3,\n  \"author\": \"NeKoOuO\",\n  \"keyframes\": [\n    {\n      \"position\": 311500,\n      \"prompt\": null\n    },\n    {\n      \"position\": 432500,\n      \"prompt\": null\n    },\n    {\n      \"position\": 824000,\n      \"prompt\": null\n    },\n    {\n      \"position\": 861300,\n      \"prompt\": null\n    }\n  ]\n}"
  },
  {
    "path": "app/src/main/assets/h_keyframes/21806.json",
    "content": "{\n  \"videoCode\": \"21806\",\n  \"group\": \"思春期SEX\",\n  \"title\": \"思春期SEX#4\",\n  \"episode\": 4,\n  \"author\": \"NeKoOuO\",\n  \"keyframes\": [\n    {\n      \"position\": 481000,\n      \"prompt\": null\n    },\n    {\n      \"position\": 657700,\n      \"prompt\": null\n    },\n    {\n      \"position\": 742700,\n      \"prompt\": null\n    },\n    {\n      \"position\": 813000,\n      \"prompt\": null\n    }\n  ]\n}"
  },
  {
    "path": "app/src/main/assets/h_keyframes/22068.json",
    "content": "{\n  \"videoCode\": \"22068\",\n  \"group\": \"思春期SEX\",\n  \"title\": \"思春期SEX#2\",\n  \"episode\": 2,\n  \"author\": \"NeKoOuO\",\n  \"keyframes\": [\n    {\n      \"position\": 324000,\n      \"prompt\": null\n    },\n    {\n      \"position\": 602000,\n      \"prompt\": null\n    },\n    {\n      \"position\": 734000,\n      \"prompt\": null\n    },\n    {\n      \"position\": 816000,\n      \"prompt\": null\n    }\n  ]\n}"
  },
  {
    "path": "app/src/main/assets/h_keyframes/37176.json",
    "content": "{\r\n  \"videoCode\": \"37176\",\r\n  \"group\": \"援助交配\",\r\n  \"title\": \"援助交配#1\",\r\n  \"episode\": 1,\r\n  \"author\": \"NeKoOuO\",\r\n  \"keyframes\": [\r\n    {\r\n      \"position\": 363600,\r\n      \"prompt\": null\r\n    },\r\n    {\r\n      \"position\": 633000,\r\n      \"prompt\": null\r\n    },\r\n    {\r\n      \"position\": 796000,\r\n      \"prompt\": null\r\n    },\r\n    {\r\n      \"position\": 881700,\r\n      \"prompt\": null\r\n    }\r\n  ]\r\n}\r\n"
  },
  {
    "path": "app/src/main/assets/h_keyframes/37177.json",
    "content": "{\r\n  \"videoCode\": \"37177\",\r\n  \"group\": \"援助交配\",\r\n  \"title\": \"援助交配#2\",\r\n  \"episode\": 2,\r\n  \"author\": \"NeKoOuO\",\r\n  \"keyframes\": [\r\n    {\r\n      \"position\": 376000,\r\n      \"prompt\": null\r\n    },\r\n    {\r\n      \"position\": 420000,\r\n      \"prompt\": null\r\n    },\r\n    {\r\n      \"position\": 600000,\r\n      \"prompt\": null\r\n    },\r\n    {\r\n      \"position\": 774000,\r\n      \"prompt\": null\r\n    },\r\n    {\r\n      \"position\": 800000,\r\n      \"prompt\": null\r\n    },\r\n    {\r\n      \"position\": 840000,\r\n      \"prompt\": null\r\n    },\r\n    {\r\n      \"position\": 856500,\r\n      \"prompt\": null\r\n    }\r\n  ]\r\n}\r\n"
  },
  {
    "path": "app/src/main/assets/h_keyframes/37178.json",
    "content": "{\r\n  \"videoCode\": \"37178\",\r\n  \"group\": \"援助交配\",\r\n  \"title\": \"援助交配#3\",\r\n  \"episode\": 3,\r\n  \"author\": \"NeKoOuO\",\r\n  \"keyframes\": [\r\n    {\r\n      \"position\": 239000,\r\n      \"prompt\": null\r\n    },\r\n    {\r\n      \"position\": 307000,\r\n      \"prompt\": null\r\n    },\r\n    {\r\n      \"position\": 439000,\r\n      \"prompt\": null\r\n    },\r\n    {\r\n      \"position\": 641000,\r\n      \"prompt\": null\r\n    },\r\n    {\r\n      \"position\": 820000,\r\n      \"prompt\": null\r\n    }\r\n  ]\r\n}\r\n"
  },
  {
    "path": "app/src/main/assets/h_keyframes/37179.json",
    "content": "{\r\n  \"videoCode\": \"37179\",\r\n  \"group\": \"援助交配\",\r\n  \"title\": \"援助交配#4\",\r\n  \"episode\": 4,\r\n  \"author\": \"NeKoOuO\",\r\n  \"keyframes\": [\r\n    {\r\n      \"position\": 36000,\r\n      \"prompt\": null\r\n    },\r\n    {\r\n      \"position\": 480000,\r\n      \"prompt\": null\r\n    },\r\n    {\r\n      \"position\": 570000,\r\n      \"prompt\": null\r\n    },\r\n    {\r\n      \"position\": 655000,\r\n      \"prompt\": null\r\n    },\r\n    {\r\n      \"position\": 800000,\r\n      \"prompt\": null\r\n    }\r\n  ]\r\n}\r\n"
  },
  {
    "path": "app/src/main/assets/h_keyframes/37220.json",
    "content": "{\n  \"videoCode\": \"37220\",\n  \"group\": \"今泉家似乎變成辣妹的聚會所\",\n  \"title\": \"今泉家似乎變成辣妹的聚會所#1\",\n  \"episode\": 1,\n  \"author\": \"NeKoOuO\",\n  \"keyframes\": [\n    {\n      \"position\": 265000,\n      \"prompt\": null\n    },\n    {\n      \"position\": 403000,\n      \"prompt\": null\n    },\n    {\n      \"position\": 493000,\n      \"prompt\": null\n    },\n    {\n      \"position\": 540000,\n      \"prompt\": null\n    },\n    {\n      \"position\": 604000,\n      \"prompt\": null\n    },\n    {\n      \"position\": 725000,\n      \"prompt\": null\n    },\n    {\n      \"position\": 769000,\n      \"prompt\": null\n    }\n  ]\n}\n"
  },
  {
    "path": "app/src/main/assets/h_keyframes/37221.json",
    "content": "\n{\n  \"videoCode\": \"37221\",\n  \"group\": \"今泉家似乎變成辣妹的聚會所\",\n  \"title\": \"今泉家似乎變成辣妹的聚會所#2\",\n  \"episode\": 2,\n  \"author\": \"NeKoOuO\",\n  \"keyframes\": [\n    {\n      \"position\": 278500,\n      \"prompt\": null\n    },\n    {\n      \"position\": 403000,\n      \"prompt\": null\n    },\n    {\n      \"position\": 510000,\n      \"prompt\": null\n    },\n    {\n      \"position\": 627000,\n      \"prompt\": null\n    },\n    {\n      \"position\": 836000,\n      \"prompt\": null\n    }\n  ]\n}\n"
  },
  {
    "path": "app/src/main/assets/h_keyframes/38387.json",
    "content": "{\r\n  \"videoCode\": \"38387\",\r\n  \"group\": \"援助交配\",\r\n  \"title\": \"援助交配#5\",\r\n  \"episode\": 5,\r\n  \"author\": \"NeKoOuO\",\r\n  \"keyframes\": [\r\n    {\r\n      \"position\": 335000,\r\n      \"prompt\": null\r\n    },\r\n    {\r\n      \"position\": 652000,\r\n      \"prompt\": null\r\n    },\r\n    {\r\n      \"position\": 817000,\r\n      \"prompt\": null\r\n    }\r\n  ]\r\n}\r\n"
  },
  {
    "path": "app/src/main/assets/h_keyframes/38389.json",
    "content": "{\n  \"videoCode\": \"38389\",\n  \"group\": \"闇憑村\",\n  \"title\": \"闇憑村#1\",\n  \"episode\": 1,\n  \"author\": \"NeKoOuO\",\n  \"keyframes\": [\n    {\n      \"position\": 290700,\n      \"prompt\": null\n    },\n    {\n      \"position\": 428000,\n      \"prompt\": null\n    },\n    {\n      \"position\": 510000,\n      \"prompt\": null\n    },\n    {\n      \"position\": 592000,\n      \"prompt\": null\n    },\n    {\n      \"position\": 798000,\n      \"prompt\": null\n    },\n    {\n      \"position\": 934500,\n      \"prompt\": null\n    }\n  ]\n}"
  },
  {
    "path": "app/src/main/assets/h_keyframes/38457.json",
    "content": "{\n  \"videoCode\": \"38457\",\n  \"group\": \"闇憑村\",\n  \"title\": \"闇憑村#2\",\n  \"episode\": 2,\n  \"author\": \"NeKoOuO\",\n  \"keyframes\": [\n    {\n      \"position\": 345300,\n      \"prompt\": null\n    },\n    {\n      \"position\": 458000,\n      \"prompt\": null\n    },\n    {\n      \"position\": 502500,\n      \"prompt\": null\n    },\n    {\n      \"position\": 507500,\n      \"prompt\": null\n    },\n    {\n      \"position\": 549000,\n      \"prompt\": null\n    },\n    {\n      \"position\": 562000,\n      \"prompt\": null\n    },\n    {\n      \"position\": 574000,\n      \"prompt\": null\n    },\n    {\n      \"position\": 686000,\n      \"prompt\": null\n    },\n    {\n      \"position\": 779500,\n      \"prompt\": null\n    },\n    {\n      \"position\": 868000,\n      \"prompt\": null\n    }\n  ]\n}"
  },
  {
    "path": "app/src/main/assets/h_keyframes/38461.json",
    "content": "{\r\n  \"videoCode\": \"38461\",\r\n  \"group\": \"援助交配\",\r\n  \"title\": \"援助交配#6\",\r\n  \"episode\": 6,\r\n  \"author\": \"NeKoOuO\",\r\n  \"keyframes\": [\r\n    {\r\n      \"position\": 86000,\r\n      \"prompt\": null\r\n    },\r\n    {\r\n      \"position\": 638000,\r\n      \"prompt\": null\r\n    },\r\n    {\r\n      \"position\": 755000,\r\n      \"prompt\": null\r\n    }\r\n  ]\r\n}\r\n"
  },
  {
    "path": "app/src/main/assets/h_keyframes/39192.json",
    "content": "{\r\n  \"videoCode\": \"39192\",\r\n  \"group\": \"援助交配\",\r\n  \"title\": \"援助交配#7\",\r\n  \"episode\": 7,\r\n  \"author\": \"NeKoOuO\",\r\n  \"keyframes\": [\r\n    {\r\n      \"position\": 260000,\r\n      \"prompt\": null\r\n    },\r\n    {\r\n      \"position\": 413500,\r\n      \"prompt\": null\r\n    },\r\n    {\r\n      \"position\": 520000,\r\n      \"prompt\": null\r\n    },\r\n    {\r\n      \"position\": 564000,\r\n      \"prompt\": null\r\n    },\r\n    {\r\n      \"position\": 656000,\r\n      \"prompt\": null\r\n    },\r\n    {\r\n      \"position\": 802000,\r\n      \"prompt\": null\r\n    }\r\n  ]\r\n}\r\n"
  },
  {
    "path": "app/src/main/assets/h_keyframes/39305.json",
    "content": "{\n  \"videoCode\": \"39305\",\n  \"group\": \"到了異世界就拿出性本事\",\n  \"title\": \"到了異世界就拿出性本事#1\",\n  \"episode\": 1,\n  \"author\": \"NeKoOuO\",\n  \"keyframes\": [\n    {\n      \"position\": 387500,\n      \"prompt\": null\n    },\n    {\n      \"position\": 457000,\n      \"prompt\": null\n    },\n    {\n      \"position\": 712500,\n      \"prompt\": null\n    },\n    {\n      \"position\": 829500,\n      \"prompt\": null\n    }\n  ]\n}\n"
  },
  {
    "path": "app/src/main/assets/h_keyframes/39306.json",
    "content": "{\n  \"videoCode\": \"39306\",\n  \"group\": \"到了異世界就拿出性本事\",\n  \"title\": \"到了異世界就拿出性本事#2\",\n  \"episode\": 2,\n  \"author\": \"NeKoOuO\",\n  \"keyframes\": [\n    {\n      \"position\": 254000,\n      \"prompt\": null\n    },\n    {\n      \"position\": 405000,\n      \"prompt\": null\n    },\n    {\n      \"position\": 502000,\n      \"prompt\": null\n    },\n    {\n      \"position\": 740500,\n      \"prompt\": null\n    },\n    {\n      \"position\": 914000,\n      \"prompt\": null\n    }\n  ]\n}\n"
  },
  {
    "path": "app/src/main/assets/h_keyframes/39468.json",
    "content": "{\r\n  \"videoCode\": \"39468\",\r\n  \"group\": \"援助交配\",\r\n  \"title\": \"援助交配#8\",\r\n  \"episode\": 8,\r\n  \"author\": \"NeKoOuO\",\r\n  \"keyframes\": [\r\n    {\r\n      \"position\": 223000,\r\n      \"prompt\": null\r\n    },\r\n    {\r\n      \"position\": 324000,\r\n      \"prompt\": null\r\n    },\r\n    {\r\n      \"position\": 608000,\r\n      \"prompt\": null\r\n    },\r\n    {\r\n      \"position\": 848000,\r\n      \"prompt\": null\r\n    }\r\n  ]\r\n}\r\n"
  },
  {
    "path": "app/src/main/assets/h_keyframes/39811.json",
    "content": "\n{\n  \"videoCode\": \"39811\",\n  \"group\": \"今泉家似乎變成辣妹的聚會所\",\n  \"title\": \"今泉家似乎變成辣妹的聚會所#4\",\n  \"episode\": 4,\n  \"author\": \"NeKoOuO\",\n  \"keyframes\": [\n    {\n      \"position\": 175000,\n      \"prompt\": null\n    },\n    {\n      \"position\": 425000,\n      \"prompt\": null\n    },\n    {\n      \"position\": 629000,\n      \"prompt\": null\n    },\n    {\n      \"position\": 825000,\n      \"prompt\": null\n    }\n  ]\n}\n"
  },
  {
    "path": "app/src/main/assets/h_keyframes/84717.json",
    "content": "{\n  \"videoCode\": \"84717\",\n  \"group\": \"初戀時間\",\n  \"title\": \"初戀時間#1\",\n  \"episode\": 1,\n  \"author\": \"NeKoOuO\",\n  \"keyframes\": [\n    {\n      \"position\": 72000,\n      \"prompt\": null\n    },\n    {\n      \"position\": 427270,\n      \"prompt\": null\n    },\n    {\n      \"position\": 499500,\n      \"prompt\": null\n    },\n    {\n      \"position\": 650000,\n      \"prompt\": null\n    },\n    {\n      \"position\": 840500,\n      \"prompt\": null\n    }\n  ]\n}\n"
  },
  {
    "path": "app/src/main/assets/h_keyframes/84718.json",
    "content": "{\n  \"videoCode\": \"84718\",\n  \"group\": \"初戀時間\",\n  \"title\": \"初戀時間#2\",\n  \"episode\": 2,\n  \"author\": \"NeKoOuO\",\n  \"keyframes\": [\n    {\n      \"position\": 462000,\n      \"prompt\": null\n    },\n    {\n      \"position\": 576500,\n      \"prompt\": null\n    },\n    {\n      \"position\": 588000,\n      \"prompt\": null\n    },\n    {\n      \"position\": 678500,\n      \"prompt\": null\n    },\n    {\n      \"position\": 823430,\n      \"prompt\": null\n    }\n  ]\n}\n"
  },
  {
    "path": "app/src/main/assets/h_keyframes/85926.json",
    "content": "{\n  \"videoCode\": \"85926\",\n  \"group\": \"初戀時間\",\n  \"title\": \"初戀時間#3\",\n  \"episode\": 3,\n  \"author\": \"NeKoOuO\",\n  \"keyframes\": [\n    {\n      \"position\": 438000,\n      \"prompt\": null\n    },\n    {\n      \"position\": 602500,\n      \"prompt\": null\n    },\n    {\n      \"position\": 822500,\n      \"prompt\": null\n    }\n  ]\n}\n"
  },
  {
    "path": "app/src/main/assets/h_keyframes/85927.json",
    "content": "{\n  \"videoCode\": \"85927\",\n  \"group\": \"初戀時間\",\n  \"title\": \"初戀時間#4\",\n  \"episode\": 4,\n  \"author\": \"NeKoOuO\",\n  \"keyframes\": [\n    {\n      \"position\": 376000,\n      \"prompt\": null\n    },\n    {\n      \"position\": 474950,\n      \"prompt\": null\n    },\n    {\n      \"position\": 633500,\n      \"prompt\": null\n    },\n    {\n      \"position\": 715000,\n      \"prompt\": null\n    }\n  ]\n}\n"
  },
  {
    "path": "app/src/main/assets/h_keyframes/87968.json",
    "content": "{\n  \"videoCode\": \"87968\",\n  \"group\": \"到了異世界就拿出性本事\",\n  \"title\": \"到了異世界就拿出性本事#3\",\n  \"episode\": 3,\n  \"author\": \"NeKoOuO\",\n  \"keyframes\": [\n    {\n      \"position\": 229500,\n      \"prompt\": null\n    },\n    {\n      \"position\": 445500,\n      \"prompt\": null\n    },\n    {\n      \"position\": 637200,\n      \"prompt\": null\n    }\n  ]\n}\n"
  },
  {
    "path": "app/src/main/assets/h_keyframes/87969.json",
    "content": "{\n  \"videoCode\": \"87969\",\n  \"group\": \"到了異世界就拿出性本事\",\n  \"title\": \"到了異世界就拿出性本事#4\",\n  \"episode\": 4,\n  \"author\": \"NeKoOuO\",\n  \"keyframes\": [\n    {\n      \"position\": 474500,\n      \"prompt\": null\n    },\n    {\n      \"position\": 791000,\n      \"prompt\": null\n    }\n  ]\n}\n"
  },
  {
    "path": "app/src/main/assets/h_keyframes/89087.json",
    "content": "{\n  \"videoCode\": \"89087\",\n  \"group\": \"初戀時間\",\n  \"title\": \"初戀時間#5\",\n  \"episode\": 5,\n  \"author\": \"NeKoOuO\",\n  \"keyframes\": [\n    {\n      \"position\": 482500,\n      \"prompt\": null\n    },\n    {\n      \"position\": 500500,\n      \"prompt\": null\n    },\n    {\n      \"position\": 556000,\n      \"prompt\": null\n    },\n    {\n      \"position\": 777300,\n      \"prompt\": null\n    }\n  ]\n}\n"
  },
  {
    "path": "app/src/main/assets/h_keyframes/89088.json",
    "content": "{\n  \"videoCode\": \"89088\",\n  \"group\": \"初戀時間\",\n  \"title\": \"初戀時間#6\",\n  \"episode\": 6,\n  \"author\": \"NeKoOuO\",\n  \"keyframes\": [\n    {\n      \"position\": 304000,\n      \"prompt\": null\n    },\n    {\n      \"position\": 387000,\n      \"prompt\": null\n    },\n    {\n      \"position\": 546000,\n      \"prompt\": null\n    },\n    {\n      \"position\": 644000,\n      \"prompt\": null\n    },\n    {\n      \"position\": 710000,\n      \"prompt\": null\n    },\n    {\n      \"position\": 808000,\n      \"prompt\": null\n    }\n  ]\n}\n"
  },
  {
    "path": "app/src/main/assets/h_keyframes/README.md",
    "content": "# 关键H帧\n\n## 何为关键H帧？\n\n就是影片到达「**顶点**」的那一刻。\n\n还在因为不能和影片内「顶点」同步，而不停地记着「顶点」的时刻然后看进度条卡时间同步吗？\n\n现在你可能不需要了。如果你开启了该功能（默认开启），并且在影片内**手动标记**每一个关键H帧，它会在 5-30s\n前在屏幕**左上角**倒计时提醒（可调整）。在 0-1s 时，会精确到一位小数，方便你更好的控制时间。\n\n此外，还提供了**共享关键H帧集**。这些关键H帧是内置于软件里的，来源于贡献者在 GitHub\n上的补充。目前是随着版本更新（或 CI 实时更新）来更新共享关键H帧集，暂时不考虑服务器在线共享之类的（太危险）。\n\n[共享关键库位置](./)\n\n## 须知\n\n1. 中字候补（生肉，字幕未上传）时**不要**上传，因为出字幕后可能会与未出字幕前**有一定的时间差**，导致时间不准。\n2. 每一个关键H帧的`prompt`尽可能短，因为太长会影响观看。\n3. 两个关键H帧必须**间隔 5s 及以上**！\n\n## 怎么共享关键H帧？\n\n> 注意：共享关键H帧是无法在程序内部修改的，只可读。\n\n### Fork & Clone\n\n先把我项目 Fork 了，然后从你项目里 Clone 到本地。\n\nfork 仓库步骤：\n\n1. 登录GitHub账号，进入要fork的仓库页面。\n2. 点击右上角的“Fork”按钮。\n3. 等待一段时间后，就可以在个人的GitHub仓库中看到已经fork过来的仓库。\n\nclone 仓库步骤：\n\n1. 在要clone的仓库页面上找到“clone with HTTPS”或“clone with SSH”按钮，并复制HTTPS或SSH地址。\n2. 在本地打开终端（或命令行界面），进入要存放仓库的文件夹。\n3. 输入命令 `git clone <复制的仓库地址>`，其中`<复制的仓库地址>`替换为刚才复制的地址。\n4. 等待一段时间后，就可以在本地文件夹中看到已经克隆过来的仓库。\n\n以上步骤完成后，就可以在本地使用这个仓库了。\n\n### 创建文件\n\n在该 md 文件所处的文件夹下创建一个 JSON 文件，命名为 `<影片代号>.json`。\n该影片代号从哪里来呢？举个例子，你所看的番，网址是`https://hanime1.me/watch?v=37453`\n，它的代号就是`v=`后面的`37453`。如果你想共享该影片的关键H帧，你需要创建名为`37453.json`的文件。\n\n### 了解并编写 JSON 结构\n\n以代号为`84711`的影片为例（内容我乱填的），就按这个格式写：\n\n```json\n{\n  \"videoCode\": \"84711\",\n  \"group\": \"aaa\",\n  \"title\": \"abc\",\n  \"episode\": 1,\n  \"author\": \"yenaly\",\n  \"keyframes\": [\n    {\n      \"position\": 1000000,\n      \"prompt\": \"abc\"\n    },\n    {\n      \"position\": 2000000,\n      \"prompt\": null\n    }\n  ]\n}\n```\n\n介绍一下每一个键值是干什么的：\n\n> 注意-1：`author`为`null`时代表个人本地储存的，不为空才代表是共享的。所以不允许 JSON 文件里出现`null`的`author`。\n>\n> 注意-2：编写前务必查询当前已上传的关键H帧是否存在。假设有一人已上传 AAA 的第一集，group 名为`aaa`，title 为`aaa-01`，你也要按照他的方式进行修改，不要自行更改 group 名为`AAA`。如果你觉得你的命名更准确、更优质，也可以选择将其所有当前系列的group名和title名一起修改。\n\n| 键        | 值类型            | 作用                                                         |\n| --------- | ----------------- | ------------------------------------------------------------ |\n| videoCode | string (not null) | 影片代号，用于识别影片                                       |\n| group     | string (nullable) | 影片系列名称，用于影片分组，为 null 代表无系列               |\n| title     | string (not null) | 影片标题，看得清晰些                                         |\n| episode   | number            | 影片集数，如果无系列请填 0                                   |\n| author    | string (not null) | 关键H帧作者，也就是你，**而不是**影片作者！                  |\n| keyframes | array             | 关键H帧列表                                                  |\n| position  | number            | 「顶点」位置，怎么获取下面说，两个 position 必须间隔 5s 及以上 |\n| prompt    | string (nullable) | 「顶点」提示，最好少于五个字，或 null                        |\n\n#### 怎么获取「顶点」位置？\n\n首先要打开关键H帧功能，然后进入该影片全屏界面，在「顶点」处暂停，长按选单栏的`🥵`按钮，会弹出一个窗口，例如：\n\n> ......\n>\n> 当前时刻：1830ms\n\n此时，`1830`就是你所需要填写的`position`数值。同时，你也可以在此时保存到你的个人本地关键H帧中。\n\n### Commit & Push\n\n将代码提交到本地仓库和推送到远程仓库的步骤如下：\n\ncommit 到本地仓库：\n\n1. 在本地文件夹中找到要修改的代码文件。\n2. 对代码进行修改、添加或删除。\n3. 使用命令 `git add <文件名>` 将修改后的文件添加到暂存区，也可以使用 `git add .` 将所有修改后的文件添加到暂存区。\n4. 使用命令 `git commit -m \"<提交信息>\"` 提交暂存区的文件到本地仓库，其中 `<提交信息>` 是本次提交的说明。\n\npush 到远程仓库：\n\n1. 使用命令 `git push origin <分支名>` 将当前分支推送到远程仓库，其中 `<分支名>`\n   是要推送的分支名称。例如，如果要将当前分支推送到远程主机的 master\n   分支，可以输入 `git push origin master`。\n\n以上步骤完成后，就可以将代码从本地仓库推送到远程仓库了。\n\n> 注意，提交信息的格式为：`[HKeyframe] 添加/修改/删除 <影片名称>`\n>\n> 简繁都行，英文也行。\n>\n> 例如：`[HKeyframe] 添加 初恋时间系列`\n\n### Pull Request\n\n从我本体的 Pull Request 里提交合并请求，如果没什么大问题就合并了。\n\n以下是Pull Request的步骤：\n\n1. 确认分支：首先，确保你已经在本地仓库中创建了一个新的分支，并且在该分支上进行了修改。\n2. 创建远程分支：将本地分支推送到远程仓库中，可以使用 `git push -u origin <分支名>`\n   命令，其中 `<分支名>` 是当前分支的名称。\n3. 打开 GitHub 页面：在浏览器中打开目标仓库的页面，并切换到要合并的分支。\n4. 创建 Pull Request：在页面上找到 “New Pull Request” 按钮，点击后会出现一个新的页面，其中包含了你推送的分支和目标仓库的信息。\n5. 填写 Pull Request 信息：在新页面中，可以填写一些关于 Pull Request\n   的信息，例如标题、描述、标签等。这些信息可以帮助其他人更好地理解你提交的代码修改。\n6. 提交 Pull Request：最后，点击 “Create Pull Request” 按钮提交 Pull Request。\n7. 等待审核：一旦提交了 Pull Request，等我审核你的修改。如果审核通过，你的修改将合并到目标仓库中。"
  },
  {
    "path": "app/src/main/assets/search_options/brands.json",
    "content": "[\n  {\n    \"name\": \"妄想実現めでぃあ\",\n    \"search_key\": \"妄想実現めでぃあ\"\n  },\n  {\n    \"name\": \"メリー・ジェーン\",\n    \"search_key\": \"メリー・ジェーン\"\n  },\n  {\n    \"name\": \"ピンクパイナップル\",\n    \"search_key\": \"ピンクパイナップル\"\n  },\n  {\n    \"name\": \"ばにぃうぉ～か～\",\n    \"search_key\": \"ばにぃうぉ～か～\"\n  },\n  {\n    \"name\": \"Queen Bee\",\n    \"search_key\": \"Queen Bee\"\n  },\n  {\n    \"name\": \"PoRO\",\n    \"search_key\": \"PoRO\"\n  },\n  {\n    \"name\": \"せるふぃっしゅ\",\n    \"search_key\": \"せるふぃっしゅ\"\n  },\n  {\n    \"name\": \"鈴木みら乃\",\n    \"search_key\": \"鈴木みら乃\"\n  },\n  {\n    \"name\": \"ショーテン\",\n    \"search_key\": \"ショーテン\"\n  },\n  {\n    \"name\": \"GOLD BEAR\",\n    \"search_key\": \"GOLD BEAR\"\n  },\n  {\n    \"name\": \"ZIZ\",\n    \"search_key\": \"ZIZ\"\n  },\n  {\n    \"name\": \"EDGE\",\n    \"search_key\": \"EDGE\"\n  },\n  {\n    \"name\": \"Collaboration Works\",\n    \"search_key\": \"Collaboration Works\"\n  },\n  {\n    \"name\": \"BOOTLEG\",\n    \"search_key\": \"BOOTLEG\"\n  },\n  {\n    \"name\": \"BOMB!CUTE!BOMB!\",\n    \"search_key\": \"BOMB!CUTE!BOMB!\"\n  },\n  {\n    \"name\": \"nur\",\n    \"search_key\": \"nur\"\n  },\n  {\n    \"name\": \"あんてきぬすっ\",\n    \"search_key\": \"あんてきぬすっ\"\n  },\n  {\n    \"name\": \"魔人\",\n    \"search_key\": \"魔人\"\n  },\n  {\n    \"name\": \"ルネ\",\n    \"search_key\": \"ルネ\"\n  },\n  {\n    \"name\": \"Princess Sugar\",\n    \"search_key\": \"Princess Sugar\"\n  },\n  {\n    \"name\": \"パシュミナ\",\n    \"search_key\": \"パシュミナ\"\n  },\n  {\n    \"name\": \"WHITE BEAR\",\n    \"search_key\": \"WHITE BEAR\"\n  },\n  {\n    \"name\": \"AniMan\",\n    \"search_key\": \"AniMan\"\n  },\n  {\n    \"name\": \"chippai\",\n    \"search_key\": \"chippai\"\n  },\n  {\n    \"name\": \"トップマーシャル\",\n    \"search_key\": \"トップマーシャル\"\n  },\n  {\n    \"name\": \"erozuki\",\n    \"search_key\": \"erozuki\"\n  },\n  {\n    \"name\": \"サークルトリビュート\",\n    \"search_key\": \"サークルトリビュート\"\n  },\n  {\n    \"name\": \"spermation\",\n    \"search_key\": \"spermation\"\n  },\n  {\n    \"name\": \"Milky\",\n    \"search_key\": \"Milky\"\n  },\n  {\n    \"name\": \"King Bee\",\n    \"search_key\": \"King Bee\"\n  },\n  {\n    \"name\": \"PashminaA\",\n    \"search_key\": \"PashminaA\"\n  },\n  {\n    \"name\": \"じゅうしぃまんご～\",\n    \"search_key\": \"じゅうしぃまんご～\"\n  },\n  {\n    \"name\": \"Hills\",\n    \"search_key\": \"Hills\"\n  },\n  {\n    \"name\": \"妄想専科\",\n    \"search_key\": \"妄想専科\"\n  },\n  {\n    \"name\": \"ディスカバリー\",\n    \"search_key\": \"ディスカバリ���\"\n  },\n  {\n    \"name\": \"ひまじん\",\n    \"search_key\": \"ひまじん\"\n  },\n  {\n    \"name\": \"37℃\",\n    \"search_key\": \"37℃\"\n  },\n  {\n    \"name\": \"schoolzone\",\n    \"search_key\": \"schoolzone\"\n  },\n  {\n    \"name\": \"GREEN BUNNY\",\n    \"search_key\": \"GREEN BUNNY\"\n  },\n  {\n    \"name\": \"バニラ\",\n    \"search_key\": \"バニラ\"\n  },\n  {\n    \"name\": \"L.\",\n    \"search_key\": \"L.\"\n  },\n  {\n    \"name\": \"PIXY\",\n    \"search_key\": \"PIXY\"\n  },\n  {\n    \"name\": \"こっとんど～る\",\n    \"search_key\": \"こっとんど～る\"\n  },\n  {\n    \"name\": \"ANIMAC\",\n    \"search_key\": \"ANIMAC\"\n  },\n  {\n    \"name\": \"Celeb\",\n    \"search_key\": \"Celeb\"\n  },\n  {\n    \"name\": \"MOON ROCK\",\n    \"search_key\": \"MOON ROCK\"\n  },\n  {\n    \"name\": \"Dream\",\n    \"search_key\": \"Dream\"\n  },\n  {\n    \"name\": \"ミンク\",\n    \"search_key\": \"ミンク\"\n  },\n  {\n    \"name\": \"オズ・インク\",\n    \"search_key\": \"オズ・インク\"\n  },\n  {\n    \"name\": \"サン出版\",\n    \"search_key\": \"サン出版\"\n  },\n  {\n    \"name\": \"ポニーキャニオン\",\n    \"search_key\": \"ポニーキャニオン\"\n  },\n  {\n    \"name\": \"わるきゅ～れ＋＋\",\n    \"search_key\": \"わるきゅ～れ＋＋\"\n  },\n  {\n    \"name\": \"株式会社虎の穴\",\n    \"search_key\": \"株式会社虎の穴\"\n  },\n  {\n    \"name\": \"エンゼルフィッシュ\",\n    \"search_key\": \"エンゼルフィッシュ\"\n  },\n  {\n    \"name\": \"UNION-CHO\",\n    \"search_key\": \"UNION-CHO\"\n  },\n  {\n    \"name\": \"TOHO\",\n    \"search_key\": \"TOHO\"\n  },\n  {\n    \"name\": \"ミルクセーキ\",\n    \"search_key\": \"ミルクセーキ\"\n  },\n  {\n    \"name\": \"2匹目のどぜう\",\n    \"search_key\": \"2匹目のどぜう\"\n  },\n  {\n    \"name\": \"じゅうしぃまんご～\",\n    \"search_key\": \"じゅうしぃまんご～\"\n  },\n  {\n    \"name\": \"ツクルノモリ\",\n    \"search_key\": \"ツクルノモリ\"\n  },\n  {\n    \"name\": \"サークルトリビュート\",\n    \"search_key\": \"サークルトリビュート\"\n  },\n  {\n    \"name\": \"トップマーシャル\",\n    \"search_key\": \"トップマーシャル\"\n  },\n  {\n    \"name\": \"彗星社\",\n    \"search_key\": \"彗星社\"\n  },\n  {\n    \"name\": \"ナチュラルハイ\",\n    \"search_key\": \"ナチュラルハイ\"\n  },\n  {\n    \"name\": \"れもんは～と\",\n    \"search_key\": \"れもんは～と\"\n  }\n]"
  },
  {
    "path": "app/src/main/assets/search_options/duration.json",
    "content": "[\n  {\n    \"lang\": {\n      \"zh-rCN\": \"全部\",\n      \"en\": \"All\",\n      \"zh-rTW\": \"全部\"\n    },\n    \"search_key\": null\n  },\n  {\n    \"lang\": {\n      \"zh-rCN\": \"1分钟+\",\n      \"en\": \"1 min+\",\n      \"zh-rTW\": \"1分鐘+\"\n    },\n    \"search_key\": \"1 分鐘 +\"\n  },\n  {\n    \"lang\": {\n      \"zh-rCN\": \"5分钟+\",\n      \"en\": \"5 min+\",\n      \"zh-rTW\": \"5分鐘+\"\n    },\n    \"search_key\": \"5 分鐘 +\"\n  },\n  {\n    \"lang\": {\n      \"zh-rCN\": \"10分钟+\",\n      \"en\": \"10 min+\",\n      \"zh-rTW\": \"10分鐘+\"\n    },\n    \"search_key\": \"10 分鐘 +\"\n  },\n  {\n    \"lang\": {\n      \"zh-rCN\": \"20分钟+\",\n      \"en\": \"20 min+\",\n      \"zh-rTW\": \"20分鐘+\"\n    },\n    \"search_key\": \"20 分鐘 +\"\n  },\n  {\n    \"lang\": {\n      \"zh-rCN\": \"30分钟+\",\n      \"en\": \"30 min+\",\n      \"zh-rTW\": \"30分鐘+\"\n    },\n    \"search_key\": \"30 分鐘 +\"\n  },\n  {\n    \"lang\": {\n      \"zh-rCN\": \"60分钟+\",\n      \"en\": \"60 min+\",\n      \"zh-rTW\": \"60分鐘+\"\n    },\n    \"search_key\": \"60 分鐘 +\"\n  },\n  {\n    \"lang\": {\n      \"zh-rCN\": \"0-10分钟\",\n      \"en\": \"0-10 min\",\n      \"zh-rTW\": \"0-10分鐘\"\n    },\n    \"search_key\": \"0 - 10 分鐘\"\n  },\n  {\n    \"lang\": {\n      \"zh-rCN\": \"0-20分钟\",\n      \"en\": \"0-20 min\",\n      \"zh-rTW\": \"0-20分鐘\"\n    },\n    \"search_key\": \"0 - 20 分鐘\"\n  }\n]"
  },
  {
    "path": "app/src/main/assets/search_options/genre.json",
    "content": "[\n  {\n    \"lang\": {\n      \"zh-rCN\": \"全部\",\n      \"en\": \"All\",\n      \"zh-rTW\": \"全部\"\n    },\n    \"search_key\": \"全部\"\n  },\n  {\n    \"lang\": {\n      \"zh-rCN\": \"里番\",\n      \"en\": \"Hentai\",\n      \"zh-rTW\": \"裏番\"\n    },\n    \"search_key\": \"裏番\"\n  },\n  {\n    \"lang\": {\n      \"zh-rCN\": \"泡面番\",\n      \"en\": \"Short Anime\",\n      \"zh-rTW\": \"泡麵番\"\n    },\n    \"search_key\": \"泡麵番\"\n  },\n  {\n    \"lang\": {\n      \"zh-rCN\": \"Motion Anime\",\n      \"en\": \"Motion Anime\",\n      \"zh-rTW\": \"Motion Anime\"\n    },\n    \"search_key\": \"Motion Anime\"\n  },\n  {\n    \"lang\": {\n      \"zh-rCN\": \"3D动画\",\n      \"en\": \"3D Animation\",\n      \"zh-rTW\": \"3D動畫\"\n    },\n    \"search_key\": \"3D動畫\"\n  },\n  {\n    \"lang\": {\n      \"zh-rCN\": \"同人作品\",\n      \"en\": \"Doujin\",\n      \"zh-rTW\": \"同人作品\"\n    },\n    \"search_key\": \"同人作品\"\n  },\n  {\n    \"lang\": {\n      \"zh-rCN\": \"MMD\",\n      \"en\": \"MMD\",\n      \"zh-rTW\": \"MMD\"\n    },\n    \"search_key\": \"MMD\"\n  },\n  {\n    \"lang\": {\n      \"zh-rCN\": \"Cosplay\",\n      \"en\": \"Cosplay\",\n      \"zh-rTW\": \"Cosplay\"\n    },\n    \"search_key\": \"Cosplay\"\n  }\n]"
  },
  {
    "path": "app/src/main/assets/search_options/sort_option.json",
    "content": "[\n  {\n    \"lang\": {\n      \"zh-rCN\": \"最新上市\",\n      \"en\": \"New Arrival\",\n      \"zh-rTW\": \"最新上市\"\n    },\n    \"search_key\": \"最新上市\"\n  },\n  {\n    \"lang\": {\n      \"zh-rCN\": \"最新上傳\",\n      \"en\": \"New Upload\",\n      \"zh-rTW\": \"最新上傳\"\n    },\n    \"search_key\": \"最新上傳\"\n  },\n  {\n    \"lang\": {\n      \"zh-rCN\": \"本日排行\",\n      \"en\": \"Daily Ranking\",\n      \"zh-rTW\": \"本日排行\"\n    },\n    \"search_key\": \"本日排行\"\n  },\n  {\n    \"lang\": {\n      \"zh-rCN\": \"本週排行\",\n      \"en\": \"Weekly Ranking\",\n      \"zh-rTW\": \"本週排行\"\n    },\n    \"search_key\": \"本週排行\"\n  },\n  {\n    \"lang\": {\n      \"zh-rCN\": \"本月排行\",\n      \"en\": \"Monthly Ranking\",\n      \"zh-rTW\": \"本月排行\"\n    },\n    \"search_key\": \"本月排行\"\n  },\n  {\n    \"lang\": {\n      \"zh-rCN\": \"观看次数\",\n      \"en\": \"View Count\",\n      \"zh-rTW\": \"觀看次數\"\n    },\n    \"search_key\": \"觀看次數\"\n  },\n  {\n    \"lang\": {\n      \"zh-rCN\": \"点赞比例\",\n      \"en\": \"Like Proportion\",\n      \"zh-rTW\": \"讚好比例\"\n    },\n    \"search_key\": \"讚好比例\"\n  },\n  {\n    \"lang\": {\n      \"zh-rCN\": \"时长最长\",\n      \"en\": \"longest Duration\",\n      \"zh-rTW\": \"時長最長\"\n    },\n    \"search_key\": \"時長最長\"\n  },\n  {\n    \"lang\": {\n      \"zh-rCN\": \"他们在看\",\n      \"en\": \"Trending\",\n      \"zh-rTW\": \"他們在看\"\n    },\n    \"search_key\": \"他們在看\"\n  }\n]"
  },
  {
    "path": "app/src/main/assets/search_options/tags.json",
    "content": "{\n  \"video_attributes\": [\n    {\n      \"lang\": {\n        \"zh-rCN\": \"无码\",\n        \"en\": \"Uncensored\",\n        \"zh-rTW\": \"無碼\"\n      },\n      \"search_key\": \"無碼\"\n    },\n    {\n      \"lang\": {\n        \"zh-rCN\": \"AI解码\",\n        \"en\": \"AI Decoded\",\n        \"zh-rTW\": \"AI解碼\"\n      },\n      \"search_key\": \"AI解碼\"\n    },\n    {\n      \"lang\": {\n        \"zh-rCN\": \"中文字幕\",\n        \"en\": \"Chinese Subtitle\",\n        \"zh-rTW\": \"中文字幕\"\n      },\n      \"search_key\": \"中文字幕\"\n    },\n    {\n      \"lang\": {\n        \"zh-rCN\": \"1080P\",\n        \"en\": \"1080P\",\n        \"zh-rTW\": \"1080P\"\n      },\n      \"search_key\": \"1080P\"\n    },\n    {\n      \"lang\": {\n        \"zh-rCN\": \"60FPS\",\n        \"en\": \"60FPS\",\n        \"zh-rTW\": \"60FPS\"\n      },\n      \"search_key\": \"60FPS\"\n    },\n    {\n      \"lang\": {\n        \"zh-rCN\": \"ASMR\",\n        \"en\": \"ASMR\",\n        \"zh-rTW\": \"ASMR\"\n      },\n      \"search_key\": \"ASMR\"\n    },\n    {\n      \"lang\": {\n        \"zh-rCN\": \"断面图\",\n        \"en\": \"Sectional View\",\n        \"zh-rTW\": \"斷面圖\"\n      },\n      \"search_key\": \"斷面圖\"\n    }\n  ],\n  \"character_relationships\": [\n    {\n      \"lang\": {\n        \"zh-rCN\": \"近亲\",\n        \"en\": \"Incest\",\n        \"zh-rTW\": \"近親\"\n      },\n      \"search_key\": \"近親\"\n    },\n    {\n      \"lang\": {\n        \"zh-rCN\": \"姐\",\n        \"en\": \"Elder Sister\",\n        \"zh-rTW\": \"姐\"\n      },\n      \"search_key\": \"姐\"\n    },\n    {\n      \"lang\": {\n        \"zh-rCN\": \"妹\",\n        \"en\": \"Younger Sister\",\n        \"zh-rTW\": \"妹\"\n      },\n      \"search_key\": \"妹\"\n    },\n    {\n      \"lang\": {\n        \"zh-rCN\": \"母\",\n        \"en\": \"Mother\",\n        \"zh-rTW\": \"母\"\n      },\n      \"search_key\": \"母\"\n    },\n    {\n      \"lang\": {\n        \"zh-rCN\": \"女儿\",\n        \"en\": \"Daughter\",\n        \"zh-rTW\": \"女兒\"\n      },\n      \"search_key\": \"女兒\"\n    },\n    {\n      \"lang\": {\n        \"zh-rCN\": \"师生\",\n        \"en\": \"Teacher Student\",\n        \"zh-rTW\": \"師生\"\n      },\n      \"search_key\": \"師生\"\n    },\n    {\n      \"lang\": {\n        \"zh-rCN\": \"情侣\",\n        \"en\": \"Couple\",\n        \"zh-rTW\": \"情侶\"\n      },\n      \"search_key\": \"情侶\"\n    },\n    {\n      \"lang\": {\n        \"zh-rCN\": \"青梅竹马\",\n        \"en\": \"Childhood Friend\",\n        \"zh-rTW\": \"青梅竹馬\"\n      },\n      \"search_key\": \"青梅竹馬\"\n    },\n    {\n      \"lang\": {\n        \"zh-rCN\": \"同事\",\n        \"en\": \"Colleague\",\n        \"zh-rTW\": \"同事\"\n      },\n      \"search_key\": \"同事\"\n    }\n  ],\n  \"characteristics\": [\n    {\n      \"lang\": {\n        \"zh-rCN\": \"JK\",\n        \"zh-rTW\": \"JK\"\n      },\n      \"search_key\": \"JK\"\n    },\n    {\n      \"lang\": {\n        \"zh-rCN\": \"处女\",\n        \"en\": \"virgin\",\n        \"zh-rTW\": \"處女\"\n      },\n      \"search_key\": \"處女\"\n    },\n    {\n      \"lang\": {\n        \"zh-rCN\": \"御姐\",\n        \"en\": \"Royal sister\",\n        \"zh-rTW\": \"御姐\"\n      },\n      \"search_key\": \"御姐\"\n    },\n    {\n      \"lang\": {\n        \"zh-rCN\": \"熟女\",\n        \"en\": \"MILF\",\n        \"zh-rTW\": \"熟女\"\n      },\n      \"search_key\": \"熟女\"\n    },\n    {\n      \"lang\": {\n        \"zh-rCN\": \"人妻\",\n        \"en\": \"wife\",\n        \"zh-rTW\": \"人妻\"\n      },\n      \"search_key\": \"人妻\"\n    },\n    {\n      \"lang\": {\n        \"zh-rCN\": \"女教师\",\n        \"en\": \"female teacher\",\n        \"zh-rTW\": \"女教師\"\n      },\n      \"search_key\": \"女教師\"\n    },\n    {\n      \"lang\": {\n        \"zh-rCN\": \"男教师\",\n        \"en\": \"male teacher \",\n        \"zh-rTW\": \"男教師\"\n      },\n      \"search_key\": \"男教師\"\n    },\n    {\n      \"lang\": {\n        \"zh-rCN\": \"女医生\",\n        \"en\": \"female doctor\",\n        \"zh-rTW\": \"女醫生\"\n      },\n      \"search_key\": \"女醫生\"\n    },\n    {\n      \"lang\": {\n        \"zh-rCN\": \"女病人\",\n        \"en\": \"female patient\",\n        \"zh-rTW\": \"女病人\"\n      },\n      \"search_key\": \"女病人\"\n    },\n    {\n      \"lang\": {\n        \"zh-rCN\": \"护士\",\n        \"en\": \"Nurse\",\n        \"zh-rTW\": \"護士\"\n      },\n      \"search_key\": \"護士\"\n    },\n    {\n      \"lang\": {\n        \"zh-rCN\": \"OL\",\n        \"zh-rTW\": \"OL\"\n      },\n      \"search_key\": \"OL\"\n    },\n    {\n      \"lang\": {\n        \"zh-rCN\": \"女警\",\n        \"en\": \"policewoman\",\n        \"zh-rTW\": \"女警\"\n      },\n      \"search_key\": \"女警\"\n    },\n    {\n      \"lang\": {\n        \"zh-rCN\": \"大小姐\",\n        \"en\": \"Miss\",\n        \"zh-rTW\": \"大小姐\"\n      },\n      \"search_key\": \"大小姐\"\n    },\n    {\n      \"lang\": {\n        \"zh-rCN\": \"偶像\",\n        \"en\": \"idol\",\n        \"zh-rTW\": \"偶像\"\n      },\n      \"search_key\": \"偶像\"\n    },\n    {\n      \"lang\": {\n        \"zh-rCN\": \"女仆\",\n        \"en\": \"maid\",\n        \"zh-rTW\": \"女僕\"\n      },\n      \"search_key\": \"女僕\"\n    },\n    {\n      \"lang\": {\n        \"zh-rCN\": \"巫女\",\n        \"en\": \"miko\",\n        \"zh-rTW\": \"巫女\"\n      },\n      \"search_key\": \"巫女\"\n    },\n    {\n      \"lang\": {\n        \"zh-rCN\": \"魔女\",\n        \"en\": \"Witch\",\n        \"zh-rTW\": \"魔女\"\n      },\n      \"search_key\": \"魔女\"\n    },\n    {\n      \"lang\": {\n        \"zh-rCN\": \"修女\",\n        \"en\": \"nun\",\n        \"zh-rTW\": \"修女\"\n      },\n      \"search_key\": \"修女\"\n    },\n    {\n      \"lang\": {\n        \"zh-rCN\": \"风俗娘\",\n        \"en\": \"custom girl\",\n        \"zh-rTW\": \"風俗娘\"\n      },\n      \"search_key\": \"風俗娘\"\n    },\n    {\n      \"lang\": {\n        \"zh-rCN\": \"公主\",\n        \"en\": \"princess\",\n        \"zh-rTW\": \"公主\"\n      },\n      \"search_key\": \"公主\"\n    },\n    {\n      \"lang\": {\n        \"zh-rCN\": \"女忍者\",\n        \"en\": \"female ninja\",\n        \"zh-rTW\": \"女忍者\"\n      },\n      \"search_key\": \"女忍者\"\n    },\n    {\n      \"lang\": {\n        \"zh-rCN\": \"女战士\",\n        \"en\": \"female warrior\",\n        \"zh-rTW\": \"女戰士\"\n      },\n      \"search_key\": \"女戰士\"\n    },\n    {\n      \"lang\": {\n        \"zh-rCN\": \"女骑士\",\n        \"en\": \"female knight\",\n        \"zh-rTW\": \"女騎士\"\n      },\n      \"search_key\": \"女騎士\"\n    },\n    {\n      \"lang\": {\n        \"zh-rCN\": \"魔法少女\",\n        \"en\": \"magical girl\",\n        \"zh-rTW\": \"魔法少女\"\n      },\n      \"search_key\": \"魔法少女\"\n    },\n    {\n      \"lang\": {\n        \"zh-rCN\": \"异种族\",\n        \"en\": \"interracial\",\n        \"zh-rTW\": \"異種族\"\n      },\n      \"search_key\": \"異種族\"\n    },\n    {\n      \"lang\": {\n        \"zh-rCN\": \"天使\",\n        \"en\": \"Angel\",\n        \"zh-rTW\": \"天使\"\n      },\n      \"search_key\": \"天使\"\n    },\n    {\n      \"lang\": {\n        \"zh-rCN\": \"妖精\",\n        \"en\": \"goblin\",\n        \"zh-rTW\": \"妖精\"\n      },\n      \"search_key\": \"妖精\"\n    },\n    {\n      \"lang\": {\n        \"zh-rCN\": \"魔物娘\",\n        \"en\": \"monster girl\",\n        \"zh-rTW\": \"魔物娘\"\n      },\n      \"search_key\": \"魔物娘\"\n    },\n    {\n      \"lang\": {\n        \"zh-rCN\": \"魅魔\",\n        \"en\": \"succubus\",\n        \"zh-rTW\": \"魅魔\"\n      },\n      \"search_key\": \"魅魔\"\n    },\n    {\n      \"lang\": {\n        \"zh-rCN\": \"吸血鬼\",\n        \"en\": \"vampire\",\n        \"zh-rTW\": \"吸血鬼\"\n      },\n      \"search_key\": \"吸血鬼\"\n    },\n    {\n      \"lang\": {\n        \"zh-rCN\": \"女鬼\",\n        \"en\": \"ghost woman\",\n        \"zh-rTW\": \"女鬼\"\n      },\n      \"search_key\": \"女鬼\"\n    },\n    {\n      \"lang\": {\n        \"zh-rCN\": \"兽娘\",\n        \"en\": \"Beast Girl\",\n        \"zh-rTW\": \"獸娘\"\n      },\n      \"search_key\": \"獸娘\"\n    },\n    {\n      \"lang\": {\n        \"zh-rCN\": \"乳牛\",\n        \"en\": \"dairy cow\",\n        \"zh-rTW\": \"乳牛\"\n      },\n      \"search_key\": \"乳牛\"\n    },\n    {\n      \"lang\": {\n        \"zh-rCN\": \"机械娘\",\n        \"en\": \"Mechanical girl\",\n        \"zh-rTW\": \"機械娘\"\n      },\n      \"search_key\": \"機械娘\"\n    },\n    {\n      \"lang\": {\n        \"zh-rCN\": \"碧池\",\n        \"en\": \"Bitike\",\n        \"zh-rTW\": \"碧池\"\n      },\n      \"search_key\": \"碧池\"\n    },\n    {\n      \"lang\": {\n        \"zh-rCN\": \"痴女\",\n        \"en\": \"Slut\",\n        \"zh-rTW\": \"痴女\"\n      },\n      \"search_key\": \"痴女\"\n    },\n    {\n      \"lang\": {\n        \"zh-rCN\": \"雌小鬼\",\n        \"en\": \"female imp\",\n        \"zh-rTW\": \"雌小鬼\"\n      },\n      \"search_key\": \"雌小鬼\"\n    },\n    {\n      \"lang\": {\n        \"zh-rCN\": \"不良少女\",\n        \"en\": \"bad girl\",\n        \"zh-rTW\": \"不良少女\"\n      },\n      \"search_key\": \"不良少女\"\n    },\n    {\n      \"lang\": {\n        \"zh-rCN\": \"傲娇\",\n        \"en\": \"Tsundere\",\n        \"zh-rTW\": \"傲嬌\"\n      },\n      \"search_key\": \"傲嬌\"\n    },\n    {\n      \"lang\": {\n        \"zh-rCN\": \"病娇\",\n        \"en\": \"yandere\",\n        \"zh-rTW\": \"病嬌\"\n      },\n      \"search_key\": \"病嬌\"\n    },\n    {\n      \"lang\": {\n        \"zh-rCN\": \"无口\",\n        \"en\": \"Mouthless\",\n        \"zh-rTW\": \"無口\"\n      },\n      \"search_key\": \"無口\"\n    },\n    {\n      \"lang\": {\n        \"zh-rCN\": \"无表情\",\n        \"en\": \"expressionless\",\n        \"zh-rTW\": \"無表情\"\n      },\n      \"search_key\": \"無表情\"\n    },\n    {\n      \"lang\": {\n        \"zh-rCN\": \"眼神死\",\n        \"en\": \"Dead eyes\",\n        \"zh-rTW\": \"眼神死\"\n      },\n      \"search_key\": \"眼神死\"\n    },\n    {\n      \"lang\": {\n        \"zh-rCN\": \"正太\",\n        \"en\": \"Shota\",\n        \"zh-rTW\": \"正太\"\n      },\n      \"search_key\": \"正太\"\n    },\n    {\n      \"lang\": {\n        \"zh-rCN\": \"伪娘\",\n        \"en\": \"femboy\",\n        \"zh-rTW\": \"偽娘\"\n      },\n      \"search_key\": \"偽娘\"\n    },\n    {\n      \"lang\": {\n        \"zh-rCN\": \"扶他\",\n        \"en\": \"Help him\",\n        \"zh-rTW\": \"扶他\"\n      },\n      \"search_key\": \"扶他\"\n    }\n  ],\n  \"appearance_and_figure\": [\n    {\n      \"lang\": {\n        \"zh-rCN\": \"短发\",\n        \"zh-rTW\": \"短髮\",\n        \"en\": \"Short Hair\"\n      },\n      \"search_key\": \"短髮\"\n    },\n    {\n      \"lang\": {\n        \"zh-rCN\": \"马尾\",\n        \"zh-rTW\": \"馬尾\",\n        \"en\": \"Ponytail\"\n      },\n      \"search_key\": \"馬尾\"\n    },\n    {\n      \"lang\": {\n        \"zh-rCN\": \"双马尾\",\n        \"zh-rTW\": \"雙馬尾\",\n        \"en\": \"Twin Tails\"\n      },\n      \"search_key\": \"雙馬尾\"\n    },\n    {\n      \"lang\": {\n        \"zh-rCN\": \"巨乳\",\n        \"zh-rTW\": \"巨乳\",\n        \"en\": \"Large Breasts\"\n      },\n      \"search_key\": \"巨乳\"\n    },\n    {\n      \"lang\": {\n        \"zh-rCN\": \"乳环\",\n        \"zh-rTW\": \"乳環\",\n        \"en\": \"Nipple Ring\"\n      },\n      \"search_key\": \"乳環\"\n    },\n    {\n      \"lang\": {\n        \"zh-rCN\": \"舌环\",\n        \"zh-rTW\": \"舌環\",\n        \"en\": \"Tongue Ring\"\n      },\n      \"search_key\": \"舌環\"\n    },\n    {\n      \"lang\": {\n        \"zh-rCN\": \"贫乳\",\n        \"zh-rTW\": \"貧乳\",\n        \"en\": \"Small Breasts\"\n      },\n      \"search_key\": \"貧乳\"\n    },\n    {\n      \"lang\": {\n        \"zh-rCN\": \"黑皮肤\",\n        \"zh-rTW\": \"黑皮膚\",\n        \"en\": \"Dark Skin\"\n      },\n      \"search_key\": \"黑皮膚\"\n    },\n    {\n      \"lang\": {\n        \"zh-rCN\": \"晒痕\",\n        \"zh-rTW\": \"曬痕\",\n        \"en\": \"Tan Lines\"\n      },\n      \"search_key\": \"曬痕\"\n    },\n    {\n      \"lang\": {\n        \"zh-rCN\": \"眼镜娘\",\n        \"zh-rTW\": \"眼鏡娘\",\n        \"en\": \"Girl with Glasses\"\n      },\n      \"search_key\": \"眼鏡娘\"\n    },\n    {\n      \"lang\": {\n        \"zh-rCN\": \"兽耳\",\n        \"zh-rTW\": \"獸耳\",\n        \"en\": \"Animal Ears\"\n      },\n      \"search_key\": \"獸耳\"\n    },\n    {\n      \"lang\": {\n        \"zh-rCN\": \"尖耳朵\",\n        \"zh-rTW\": \"尖耳朵\",\n        \"en\": \"Pointed Ears\"\n      },\n      \"search_key\": \"尖耳朵\"\n    },\n    {\n      \"lang\": {\n        \"zh-rCN\": \"异色瞳\",\n        \"zh-rTW\": \"異色瞳\",\n        \"en\": \"Heterochromia\"\n      },\n      \"search_key\": \"異色瞳\"\n    },\n    {\n      \"lang\": {\n        \"zh-rCN\": \"美人痣\",\n        \"zh-rTW\": \"美人痣\",\n        \"en\": \"Beauty Mark\"\n      },\n      \"search_key\": \"美人痣\"\n    },\n    {\n      \"lang\": {\n        \"zh-rCN\": \"肌肉女\",\n        \"zh-rTW\": \"肌肉女\",\n        \"en\": \"Muscular Female\"\n      },\n      \"search_key\": \"肌肉女\"\n    },\n    {\n      \"lang\": {\n        \"zh-rCN\": \"白虎\",\n        \"zh-rTW\": \"白虎\",\n        \"en\": \"Hairless\"\n      },\n      \"search_key\": \"白虎\"\n    },\n    {\n      \"lang\": {\n        \"zh-rCN\": \"阴毛\",\n        \"zh-rTW\": \"陰毛\",\n        \"en\": \"Pubic Hair\"\n      },\n      \"search_key\": \"陰毛\"\n    },\n    {\n      \"lang\": {\n        \"zh-rCN\": \"腋毛\",\n        \"zh-rTW\": \"腋毛\",\n        \"en\": \"Armpit Hair\"\n      },\n      \"search_key\": \"腋毛\"\n    },\n    {\n      \"lang\": {\n        \"zh-rCN\": \"大屌\",\n        \"zh-rTW\": \"大屌\",\n        \"en\": \"Big Dick\"\n      },\n      \"search_key\": \"大屌\"\n    },\n    {\n      \"lang\": {\n        \"zh-rCN\": \"水手服\",\n        \"zh-rTW\": \"水手服\",\n        \"en\": \"Sailor Uniform\"\n      },\n      \"search_key\": \"水手服\"\n    },\n    {\n      \"lang\": {\n        \"zh-rCN\": \"体操服\",\n        \"zh-rTW\": \"體操服\",\n        \"en\": \"Gym Uniform\"\n      },\n      \"search_key\": \"體操服\"\n    },\n    {\n      \"lang\": {\n        \"zh-rCN\": \"泳装\",\n        \"zh-rTW\": \"泳裝\",\n        \"en\": \"Swimsuit\"\n      },\n      \"search_key\": \"泳裝\"\n    },\n    {\n      \"lang\": {\n        \"zh-rCN\": \"比基尼\",\n        \"zh-rTW\": \"比基尼\",\n        \"en\": \"Bikini\"\n      },\n      \"search_key\": \"比基尼\"\n    },\n    {\n      \"lang\": {\n        \"zh-rCN\": \"死库水\",\n        \"zh-rTW\": \"死庫水\",\n        \"en\": \"School Swimsuit\"\n      },\n      \"search_key\": \"死庫水\"\n    },\n    {\n      \"lang\": {\n        \"zh-rCN\": \"和服\",\n        \"zh-rTW\": \"和服\",\n        \"en\": \"Kimono\"\n      },\n      \"search_key\": \"和服\"\n    },\n    {\n      \"lang\": {\n        \"zh-rCN\": \"兔女郎\",\n        \"zh-rTW\": \"兔女郎\",\n        \"en\": \"Bunny Girl\"\n      },\n      \"search_key\": \"兔女郎\"\n    },\n    {\n      \"lang\": {\n        \"zh-rCN\": \"围裙\",\n        \"zh-rTW\": \"圍裙\",\n        \"en\": \"Apron\"\n      },\n      \"search_key\": \"圍裙\"\n    },\n    {\n      \"lang\": {\n        \"zh-rCN\": \"啦啦队\",\n        \"zh-rTW\": \"啦啦隊\",\n        \"en\": \"Cheerleader\"\n      },\n      \"search_key\": \"啦啦隊\"\n    },\n    {\n      \"lang\": {\n        \"zh-rCN\": \"丝袜\",\n        \"zh-rTW\": \"絲襪\",\n        \"en\": \"Stockings\"\n      },\n      \"search_key\": \"絲襪\"\n    },\n    {\n      \"lang\": {\n        \"zh-rCN\": \"吊袜带\",\n        \"zh-rTW\": \"吊襪帶\",\n        \"en\": \"Garter Belt\"\n      },\n      \"search_key\": \"吊襪帶\"\n    },\n    {\n      \"lang\": {\n        \"zh-rCN\": \"热裤\",\n        \"zh-rTW\": \"熱褲\",\n        \"en\": \"Short Shorts\"\n      },\n      \"search_key\": \"熱褲\"\n    },\n    {\n      \"lang\": {\n        \"zh-rCN\": \"迷你裙\",\n        \"zh-rTW\": \"迷你裙\",\n        \"en\": \"Miniskirt\"\n      },\n      \"search_key\": \"迷你裙\"\n    },\n    {\n      \"lang\": {\n        \"zh-rCN\": \"性感内衣\",\n        \"zh-rTW\": \"性感內衣\",\n        \"en\": \"Sexy Lingerie\"\n      },\n      \"search_key\": \"性感內衣\"\n    },\n    {\n      \"lang\": {\n        \"zh-rCN\": \"紧身衣\",\n        \"zh-rTW\": \"緊身衣\",\n        \"en\": \"Bodysuit\"\n      },\n      \"search_key\": \"緊身衣\"\n    },\n    {\n      \"lang\": {\n        \"zh-rCN\": \"丁字裤\",\n        \"zh-rTW\": \"丁字褲\",\n        \"en\": \"Thong\"\n      },\n      \"search_key\": \"丁字褲\"\n    },\n    {\n      \"lang\": {\n        \"zh-rCN\": \"高跟鞋\",\n        \"zh-rTW\": \"高跟鞋\",\n        \"en\": \"High Heels\"\n      },\n      \"search_key\": \"高跟鞋\"\n    },\n    {\n      \"lang\": {\n        \"zh-rCN\": \"婚纱\",\n        \"zh-rTW\": \"婚紗\",\n        \"en\": \"Wedding Dress\"\n      },\n      \"search_key\": \"婚紗\"\n    },\n    {\n      \"lang\": {\n        \"zh-rCN\": \"旗袍\",\n        \"zh-rTW\": \"旗袍\",\n        \"en\": \"Cheongsam\"\n      },\n      \"search_key\": \"旗袍\"\n    },\n    {\n      \"lang\": {\n        \"zh-rCN\": \"古装\",\n        \"zh-rTW\": \"古裝\",\n        \"en\": \"Traditional Chinese Clothing\"\n      },\n      \"search_key\": \"古裝\"\n    },\n    {\n      \"lang\": {\n        \"zh-rCN\": \"哥德萝莉塔\",\n        \"zh-rTW\": \"哥德蘿莉塔\",\n        \"en\": \"Gothic Lolita\"\n      },\n      \"search_key\": \"哥德蘿莉塔\"\n    },\n    {\n      \"lang\": {\n        \"zh-rCN\": \"口罩\",\n        \"zh-rTW\": \"口罩\",\n        \"en\": \"Mask\"\n      },\n      \"search_key\": \"口罩\"\n    },\n    {\n      \"lang\": {\n        \"zh-rCN\": \"刺青\",\n        \"zh-rTW\": \"刺青\",\n        \"en\": \"Tattoo\"\n      },\n      \"search_key\": \"刺青\"\n    },\n    {\n      \"lang\": {\n        \"zh-rCN\": \"淫纹\",\n        \"zh-rTW\": \"淫紋\",\n        \"en\": \"Lewd Tattoo\"\n      },\n      \"search_key\": \"淫紋\"\n    },\n    {\n      \"lang\": {\n        \"zh-rCN\": \"身体写字\",\n        \"zh-rTW\": \"身體寫字\",\n        \"en\": \"Body Writing\"\n      },\n      \"search_key\": \"身體寫字\"\n    }\n  ],\n  \"story_plot\": [\n    {\n      \"lang\": {\n        \"zh-rCN\": \"纯爱\",\n        \"en\": \"pure love\",\n        \"zh-rTW\": \"純愛\"\n      },\n      \"search_key\": \"纯爱\"\n    },\n    {\n      \"lang\": {\n        \"zh-rCN\": \"恋爱喜剧\",\n        \"en\": \"romantic comedy\",\n        \"zh-rTW\": \"戀愛喜劇\"\n      },\n      \"search_key\": \"恋爱喜剧\"\n    },\n    {\n      \"lang\": {\n        \"zh-rCN\": \"后宫\",\n        \"en\": \"harem\",\n        \"zh-rTW\": \"後宮\"\n      },\n      \"search_key\": \"后宫\"\n    },\n    {\n      \"lang\": {\n        \"zh-rCN\": \"开大车\",\n        \"en\": \"drive big\",\n        \"zh-rTW\": \"開大車\"\n      },\n      \"search_key\": \"开大车\"\n    },\n    {\n      \"lang\": {\n        \"zh-rCN\": \"校园\",\n        \"en\": \"campus\",\n        \"zh-rTW\": \"校園\"\n      },\n      \"search_key\": \"校园\"\n    },\n    {\n      \"lang\": {\n        \"zh-rCN\": \"教室\",\n        \"en\": \"classroom\",\n        \"zh-rTW\": \"教室\"\n      },\n      \"search_key\": \"教室\"\n    },\n    {\n      \"lang\": {\n        \"zh-rCN\": \"公众场合\",\n        \"en\": \"public places\",\n        \"zh-rTW\": \"公眾場合\"\n      },\n      \"search_key\": \"公众场合\"\n    },\n    {\n      \"lang\": {\n        \"zh-rCN\": \"公共厕所\",\n        \"en\": \"public toilet\",\n        \"zh-rTW\": \"公共廁所\"\n      },\n      \"search_key\": \"公共厕所\"\n    },\n    {\n      \"lang\": {\n        \"zh-rCN\": \"NTR\",\n        \"zh-rTW\": \"NTR\"\n      },\n      \"search_key\": \"NTR\"\n    },\n    {\n      \"lang\": {\n        \"zh-rCN\": \"精神控制\",\n        \"en\": \"mind control\",\n        \"zh-rTW\": \"精神控制\"\n      },\n      \"search_key\": \"精神控制\"\n    },\n    {\n      \"lang\": {\n        \"zh-rCN\": \"药物\",\n        \"en\": \"drug\",\n        \"zh-rTW\": \"藥物\"\n      },\n      \"search_key\": \"药物\"\n    },\n    {\n      \"lang\": {\n        \"zh-rCN\": \"痴汉\",\n        \"en\": \"Idiot\",\n        \"zh-rTW\": \"痴漢\"\n      },\n      \"search_key\": \"痴汉\"\n    },\n    {\n      \"lang\": {\n        \"zh-rCN\": \"阿嘿颜\",\n        \"en\": \"orgasm face\",\n        \"zh-rTW\": \"阿嘿顏\"\n      },\n      \"search_key\": \"阿嘿颜\"\n    },\n    {\n      \"lang\": {\n        \"zh-rCN\": \"精神崩溃\",\n        \"en\": \"nervous breakdown\",\n        \"zh-rTW\": \"精神崩潰\"\n      },\n      \"search_key\": \"精神崩溃\"\n    },\n    {\n      \"lang\": {\n        \"zh-rCN\": \"猎奇\",\n        \"en\": \"Curiosity\",\n        \"zh-rTW\": \"獵奇\"\n      },\n      \"search_key\": \"猎奇\"\n    },\n    {\n      \"lang\": {\n        \"zh-rCN\": \"BDSM\",\n        \"zh-rTW\": \"BDSM\"\n      },\n      \"search_key\": \"BDSM\"\n    },\n    {\n      \"lang\": {\n        \"zh-rCN\": \"捆绑\",\n        \"en\": \"bundle\",\n        \"zh-rTW\": \"綑綁\"\n      },\n      \"search_key\": \"捆绑\"\n    },\n    {\n      \"lang\": {\n        \"zh-rCN\": \"眼罩\",\n        \"en\": \"blindfold\",\n        \"zh-rTW\": \"眼罩\"\n      },\n      \"search_key\": \"眼罩\"\n    },\n    {\n      \"lang\": {\n        \"zh-rCN\": \"项圈\",\n        \"en\": \"collar\",\n        \"zh-rTW\": \"項圈\"\n      },\n      \"search_key\": \"项圈\"\n    },\n    {\n      \"lang\": {\n        \"zh-rCN\": \"调教\",\n        \"en\": \"training\",\n        \"zh-rTW\": \"調教\"\n      },\n      \"search_key\": \"调教\"\n    },\n    {\n      \"lang\": {\n        \"zh-rCN\": \"异物插入\",\n        \"en\": \"Foreign body insertion\",\n        \"zh-rTW\": \"異物插入\"\n      },\n      \"search_key\": \"异物插入\"\n    },\n    {\n      \"lang\": {\n        \"zh-rCN\": \"寻欢洞\",\n        \"en\": \"glory hole\",\n        \"zh-rTW\": \"尋歡洞\"\n      },\n      \"search_key\": \"寻欢洞\"\n    },\n    {\n      \"lang\": {\n        \"zh-rCN\": \"肉便器\",\n        \"en\": \"meat toilet\",\n        \"zh-rTW\": \"肉便器\"\n      },\n      \"search_key\": \"肉便器\"\n    },\n    {\n      \"lang\": {\n        \"zh-rCN\": \"性奴隶\",\n        \"en\": \"slave\",\n        \"zh-rTW\": \"性奴隸\"\n      },\n      \"search_key\": \"性奴隶\"\n    },\n    {\n      \"lang\": {\n        \"zh-rCN\": \"胃凸\",\n        \"en\": \"Stomach protrusion\",\n        \"zh-rTW\": \"胃凸\"\n      },\n      \"search_key\": \"胃凸\"\n    },\n    {\n      \"lang\": {\n        \"zh-rCN\": \"强制\",\n        \"en\": \"forced\",\n        \"zh-rTW\": \"強制\"\n      },\n      \"search_key\": \"强制\"\n    },\n    {\n      \"lang\": {\n        \"zh-rCN\": \"轮奸\",\n        \"en\": \"gang rape\",\n        \"zh-rTW\": \"輪姦\"\n      },\n      \"search_key\": \"轮奸\"\n    },\n    {\n      \"lang\": {\n        \"zh-rCN\": \"凌辱\",\n        \"en\": \"insult\",\n        \"zh-rTW\": \"凌辱\"\n      },\n      \"search_key\": \"凌辱\"\n    },\n    {\n      \"lang\": {\n        \"zh-rCN\": \"扯头发\",\n        \"en\": \"pull hair\",\n        \"zh-rTW\": \"扯頭髮\"\n      },\n      \"search_key\": \"扯头发\"\n    },\n    {\n      \"lang\": {\n        \"zh-rCN\": \"打屁股\",\n        \"en\": \"spanking\",\n        \"zh-rTW\": \"打屁股\"\n      },\n      \"search_key\": \"打屁股\"\n    },\n    {\n      \"lang\": {\n        \"zh-rCN\": \"肉棒打脸\",\n        \"en\": \"Cock slapping face\",\n        \"zh-rTW\": \"肉棒打臉\"\n      },\n      \"search_key\": \"肉棒打脸\"\n    },\n    {\n      \"lang\": {\n        \"zh-rCN\": \"性暴力\",\n        \"en\": \"sexual violence\",\n        \"zh-rTW\": \"性暴力\"\n      },\n      \"search_key\": \"性暴力\"\n    },\n    {\n      \"lang\": {\n        \"zh-rCN\": \"逆强制\",\n        \"en\": \"Counter coercion\",\n        \"zh-rTW\": \"逆強制\"\n      },\n      \"search_key\": \"逆强制\"\n    },\n    {\n      \"lang\": {\n        \"zh-rCN\": \"女王样\",\n        \"en\": \"Queenlike\",\n        \"zh-rTW\": \"女王樣\"\n      },\n      \"search_key\": \"女王样\"\n    },\n    {\n      \"lang\": {\n        \"zh-rCN\": \"母女丼\",\n        \"en\": \"Mother and Daughter Donburi\",\n        \"zh-rTW\": \"母女丼\"\n      },\n      \"search_key\": \"母女丼\"\n    },\n    {\n      \"lang\": {\n        \"zh-rCN\": \"姐妹丼\",\n        \"en\": \"Sisters' Donburi\",\n        \"zh-rTW\": \"姐妹丼\"\n      },\n      \"search_key\": \"姐妹丼\"\n    },\n    {\n      \"lang\": {\n        \"zh-rCN\": \"出轨\",\n        \"en\": \"Cheating\",\n        \"zh-rTW\": \"出軌\"\n      },\n      \"search_key\": \"出轨\"\n    },\n    {\n      \"lang\": {\n        \"zh-rCN\": \"醉酒\",\n        \"en\": \"Drunk\",\n        \"zh-rTW\": \"醉酒\"\n      },\n      \"search_key\": \"醉酒\"\n    },\n    {\n      \"lang\": {\n        \"zh-rCN\": \"摄影\",\n        \"en\": \"photography\",\n        \"zh-rTW\": \"攝影\"\n      },\n      \"search_key\": \"摄影\"\n    },\n    {\n      \"lang\": {\n        \"zh-rCN\": \"睡眠奸\",\n        \"en\": \"sleep rape\",\n        \"zh-rTW\": \"睡眠姦\"\n      },\n      \"search_key\": \"睡眠奸\"\n    },\n    {\n      \"lang\": {\n        \"zh-rCN\": \"机械奸\",\n        \"en\": \"Mechanical rape\",\n        \"zh-rTW\": \"機械姦\"\n      },\n      \"search_key\": \"机械奸\"\n    },\n    {\n      \"lang\": {\n        \"zh-rCN\": \"虫奸\",\n        \"en\": \"Insect rape\",\n        \"zh-rTW\": \"蟲姦\"\n      },\n      \"search_key\": \"虫奸\"\n    },\n    {\n      \"lang\": {\n        \"zh-rCN\": \"性转换\",\n        \"en\": \"sexual conversion\",\n        \"zh-rTW\": \"性轉換\"\n      },\n      \"search_key\": \"性转换\"\n    },\n    {\n      \"lang\": {\n        \"zh-rCN\": \"百合\",\n        \"en\": \"lily\",\n        \"zh-rTW\": \"百合\"\n      },\n      \"search_key\": \"百合\"\n    },\n    {\n      \"lang\": {\n        \"zh-rCN\": \"耽美\",\n        \"en\": \"BL\",\n        \"zh-rTW\": \"耽美\"\n      },\n      \"search_key\": \"耽美\"\n    },\n    {\n      \"lang\": {\n        \"zh-rCN\": \"时间停止\",\n        \"en\": \"time stop\",\n        \"zh-rTW\": \"時間停止\"\n      },\n      \"search_key\": \"时间停止\"\n    },\n    {\n      \"lang\": {\n        \"zh-rCN\": \"异世界\",\n        \"en\": \"Another World\",\n        \"zh-rTW\": \"異世界\"\n      },\n      \"search_key\": \"异世界\"\n    },\n    {\n      \"lang\": {\n        \"zh-rCN\": \"怪兽\",\n        \"en\": \"monster\",\n        \"zh-rTW\": \"怪獸\"\n      },\n      \"search_key\": \"怪兽\"\n    },\n    {\n      \"lang\": {\n        \"zh-rCN\": \"哥布林\",\n        \"en\": \"Goblin\",\n        \"zh-rTW\": \"哥布林\"\n      },\n      \"search_key\": \"哥布林\"\n    },\n    {\n      \"lang\": {\n        \"zh-rCN\": \"世界末日\",\n        \"en\": \"end of the world\",\n        \"zh-rTW\": \"世界末日\"\n      },\n      \"search_key\": \"世界末日\"\n    }\n  ],\n  \"sex_positions\": [\n    {\n      \"lang\": {\n        \"zh-rCN\": \"手交\",\n        \"en\": \"handjob\",\n        \"zh-rTW\": \"手交\"\n      },\n      \"search_key\": \"手交\"\n    },\n    {\n      \"lang\": {\n        \"zh-rCN\": \"指交\",\n        \"en\": \"fingering\",\n        \"zh-rTW\": \"指交\"\n      },\n      \"search_key\": \"指交\"\n    },\n    {\n      \"lang\": {\n        \"zh-rCN\": \"乳交\",\n        \"en\": \"Titjob\",\n        \"zh-rTW\": \"乳交\"\n      },\n      \"search_key\": \"乳交\"\n    },\n    {\n      \"lang\": {\n        \"zh-rCN\": \"乳头交\",\n        \"en\": \"mammary intercourse\",\n        \"zh-rTW\": \"乳頭交\"\n      },\n      \"search_key\": \"乳頭交\"\n    },\n    {\n      \"lang\": {\n        \"zh-rCN\": \"肛交\",\n        \"en\": \"anal\",\n        \"zh-rTW\": \"肛交\"\n      },\n      \"search_key\": \"肛交\"\n    },\n    {\n      \"lang\": {\n        \"zh-rCN\": \"双洞齐下\",\n        \"en\": \"Double holes\",\n        \"zh-rTW\": \"雙洞齊下\"\n      },\n      \"search_key\": \"雙洞齊下\"\n    },\n    {\n      \"lang\": {\n        \"zh-rCN\": \"脚交\",\n        \"en\": \"footjob\",\n        \"zh-rTW\": \"腳交\"\n      },\n      \"search_key\": \"腳交\"\n    },\n    {\n      \"lang\": {\n        \"zh-rCN\": \"素股\",\n        \"en\": \"Plain stocks\",\n        \"zh-rTW\": \"素股\"\n      },\n      \"search_key\": \"素股\"\n    },\n    {\n      \"lang\": {\n        \"zh-rCN\": \"拳交\",\n        \"en\": \"fisting\",\n        \"zh-rTW\": \"拳交\"\n      },\n      \"search_key\": \"拳交\"\n    },\n    {\n      \"lang\": {\n        \"zh-rCN\": \"3P\",\n        \"zh-rTW\": \"3P\"\n      },\n      \"search_key\": \"3P\"\n    },\n    {\n      \"lang\": {\n        \"zh-rCN\": \"群交\",\n        \"en\": \"Group sex\",\n        \"zh-rTW\": \"群交\"\n      },\n      \"search_key\": \"群交\"\n    },\n    {\n      \"lang\": {\n        \"zh-rCN\": \"口交\",\n        \"en\": \"oral sex\",\n        \"zh-rTW\": \"口交\"\n      },\n      \"search_key\": \"口交\"\n    },\n    {\n      \"lang\": {\n        \"zh-rCN\": \"深喉咙\",\n        \"en\": \"Deep Throat\",\n        \"zh-rTW\": \"深喉嚨\"\n      },\n      \"search_key\": \"深喉嚨\"\n    },\n    {\n      \"lang\": {\n        \"zh-rCN\": \"口爆\",\n        \"en\": \"oral sex\",\n        \"zh-rTW\": \"口爆\"\n      },\n      \"search_key\": \"口爆\"\n    },\n    {\n      \"lang\": {\n        \"zh-rCN\": \"吞精\",\n        \"en\": \"swallow semen\",\n        \"zh-rTW\": \"吞精\"\n      },\n      \"search_key\": \"吞精\"\n    },\n    {\n      \"lang\": {\n        \"zh-rCN\": \"舔蛋蛋\",\n        \"en\": \"Licking balls\",\n        \"zh-rTW\": \"舔蛋蛋\"\n      },\n      \"search_key\": \"舔蛋蛋\"\n    },\n    {\n      \"lang\": {\n        \"zh-rCN\": \"舔穴\",\n        \"en\": \"Licking pussy\",\n        \"zh-rTW\": \"舔穴\"\n      },\n      \"search_key\": \"舔穴\"\n    },\n    {\n      \"lang\": {\n        \"zh-rCN\": \"69\",\n        \"zh-rTW\": \"69\"\n      },\n      \"search_key\": \"69\"\n    },\n    {\n      \"lang\": {\n        \"zh-rCN\": \"自慰\",\n        \"en\": \"masturbate\",\n        \"zh-rTW\": \"自慰\"\n      },\n      \"search_key\": \"自慰\"\n    },\n    {\n      \"lang\": {\n        \"zh-rCN\": \"腋交\",\n        \"en\": \"armpit sex\",\n        \"zh-rTW\": \"腋交\"\n      },\n      \"search_key\": \"腋交\"\n    },\n    {\n      \"lang\": {\n        \"zh-rCN\": \"舔腋下\",\n        \"en\": \"lick armpits\",\n        \"zh-rTW\": \"舔腋下\"\n      },\n      \"search_key\": \"舔腋下\"\n    },\n    {\n      \"lang\": {\n        \"zh-rCN\": \"发交\",\n        \"en\": \"Send\",\n        \"zh-rTW\": \"髮交\"\n      },\n      \"search_key\": \"髮交\"\n    },\n    {\n      \"lang\": {\n        \"zh-rCN\": \"舔耳朵\",\n        \"en\": \"Licking ears\",\n        \"zh-rTW\": \"舔耳朵\"\n      },\n      \"search_key\": \"舔耳朵\"\n    },\n    {\n      \"lang\": {\n        \"zh-rCN\": \"内射\",\n        \"en\": \"Creampie\",\n        \"zh-rTW\": \"內射\"\n      },\n      \"search_key\": \"內射\"\n    },\n    {\n      \"lang\": {\n        \"zh-rCN\": \"外射\",\n        \"en\": \"ejaculate externally\",\n        \"zh-rTW\": \"外射\"\n      },\n      \"search_key\": \"外射\"\n    },\n    {\n      \"lang\": {\n        \"zh-rCN\": \"颜射\",\n        \"en\": \"Facial cumshot\",\n        \"zh-rTW\": \"顏射\"\n      },\n      \"search_key\": \"顏射\"\n    },\n    {\n      \"lang\": {\n        \"zh-rCN\": \"潮吹\",\n        \"en\": \"squirt\",\n        \"zh-rTW\": \"潮吹\"\n      },\n      \"search_key\": \"潮吹\"\n    },\n    {\n      \"lang\": {\n        \"zh-rCN\": \"怀孕\",\n        \"en\": \"pregnant\",\n        \"zh-rTW\": \"懷孕\"\n      },\n      \"search_key\": \"懷孕\"\n    },\n    {\n      \"lang\": {\n        \"zh-rCN\": \"喷奶\",\n        \"en\": \"Squirt\",\n        \"zh-rTW\": \"噴奶\"\n      },\n      \"search_key\": \"噴奶\"\n    },\n    {\n      \"lang\": {\n        \"zh-rCN\": \"放尿\",\n        \"en\": \"urinate\",\n        \"zh-rTW\": \"放尿\"\n      },\n      \"search_key\": \"放尿\"\n    },\n    {\n      \"lang\": {\n        \"zh-rCN\": \"排便\",\n        \"en\": \"Defecation\",\n        \"zh-rTW\": \"排便\"\n      },\n      \"search_key\": \"排便\"\n    },\n    {\n      \"lang\": {\n        \"zh-rCN\": \"骑乘位\",\n        \"en\": \"riding\",\n        \"zh-rTW\": \"騎乘位\"\n      },\n      \"search_key\": \"騎乘位\"\n    },\n    {\n      \"lang\": {\n        \"zh-rCN\": \"背后位\",\n        \"en\": \"Behind back\",\n        \"zh-rTW\": \"背後位\"\n      },\n      \"search_key\": \"背後位\"\n    },\n    {\n      \"lang\": {\n        \"zh-rCN\": \"颜面骑乘\",\n        \"en\": \"Facesitting\",\n        \"zh-rTW\": \"顏面騎乘\"\n      },\n      \"search_key\": \"顏面騎乘\"\n    },\n    {\n      \"lang\": {\n        \"zh-rCN\": \"火车便当\",\n        \"en\": \"train lunch\",\n        \"zh-rTW\": \"火車便當\"\n      },\n      \"search_key\": \"火車便當\"\n    },\n    {\n      \"lang\": {\n        \"zh-rCN\": \"车震\",\n        \"en\": \"car shock\",\n        \"zh-rTW\": \"車震\"\n      },\n      \"search_key\": \"車震\"\n    },\n    {\n      \"lang\": {\n        \"zh-rCN\": \"性玩具\",\n        \"en\": \"sex toys\",\n        \"zh-rTW\": \"性玩具\"\n      },\n      \"search_key\": \"性玩具\"\n    },\n    {\n      \"lang\": {\n        \"zh-rCN\": \"飞机杯\",\n        \"en\": \"airplane cup\",\n        \"zh-rTW\": \"飛機杯\"\n      },\n      \"search_key\": \"飛機杯\"\n    },\n    {\n      \"lang\": {\n        \"zh-rCN\": \"跳蛋\",\n        \"en\": \"Vibrator\",\n        \"zh-rTW\": \"跳蛋\"\n      },\n      \"search_key\": \"跳蛋\"\n    },\n    {\n      \"lang\": {\n        \"zh-rCN\": \"毒龙钻\",\n        \"en\": \"Poison Dragon Diamond\",\n        \"zh-rTW\": \"毒龍鑽\"\n      },\n      \"search_key\": \"毒龍鑽\"\n    },\n    {\n      \"lang\": {\n        \"zh-rCN\": \"触手\",\n        \"en\": \"tentacle\",\n        \"zh-rTW\": \"觸手\"\n      },\n      \"search_key\": \"觸手\"\n    },\n    {\n      \"lang\": {\n        \"zh-rCN\": \"兽交\",\n        \"en\": \"bestiality\",\n        \"zh-rTW\": \"獸交\"\n      },\n      \"search_key\": \"獸交\"\n    },\n    {\n      \"lang\": {\n        \"zh-rCN\": \"颈手枷\",\n        \"en\": \"pillory\",\n        \"zh-rTW\": \"頸手枷\"\n      },\n      \"search_key\": \"頸手枷\"\n    },\n    {\n      \"lang\": {\n        \"zh-rCN\": \"着衣\",\n        \"en\": \"dress\",\n        \"zh-rTW\": \"著衣\"\n      },\n      \"search_key\": \"著衣\"\n    },\n    {\n      \"lang\": {\n        \"zh-rCN\": \"阴道外翻\",\n        \"en\": \"vaginal eversion\",\n        \"zh-rTW\": \"陰道外翻\"\n      },\n      \"search_key\": \"陰道外翻\"\n    },\n    {\n      \"lang\": {\n        \"zh-rCN\": \"接吻\",\n        \"en\": \"kiss\",\n        \"zh-rTW\": \"接吻\"\n      },\n      \"search_key\": \"接吻\"\n    },\n    {\n      \"lang\": {\n        \"zh-rCN\": \"舌吻\",\n        \"en\": \"French kiss\",\n        \"zh-rTW\": \"舌吻\"\n      },\n      \"search_key\": \"舌吻\"\n    },\n    {\n      \"lang\": {\n        \"zh-rCN\": \"POV\",\n        \"zh-rTW\": \"POV\"\n      },\n      \"search_key\": \"POV\"\n    }\n  ]\n}\n"
  },
  {
    "path": "app/src/main/assets/shaders/Anime4K_AutoDownscalePre_x2.glsl",
    "content": "// This is free and unencumbered software released into the public domain.\n\n// Anyone is free to copy, modify, publish, use, compile, sell, or\n// distribute this software, either in source code form or as a compiled\n// binary, for any purpose, commercial or non-commercial, and by any\n// means.\n\n// In jurisdictions that recognize copyright laws, the author or authors\n// of this software dedicate any and all copyright interest in the\n// software to the public domain. We make this dedication for the benefit\n// of the public at large and to the detriment of our heirs and\n// successors. We intend this dedication to be an overt act of\n// relinquishment in perpetuity of all present and future rights to this\n// software under copyright law.\n\n// THE SOFTWARE IS PROVIDED \"AS IS\", WITHOUT WARRANTY OF ANY KIND,\n// EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF\n// MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.\n// IN NO EVENT SHALL THE AUTHORS BE LIABLE FOR ANY CLAIM, DAMAGES OR\n// OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE,\n// ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR\n// OTHER DEALINGS IN THE SOFTWARE.\n\n// For more information, please refer to <https://unlicense.org>\n\n//!DESC Anime4K-v4.0-AutoDownscalePre-x2\n//!HOOK MAIN\n//!BIND HOOKED\n//!BIND NATIVE\n//!WHEN OUTPUT.w NATIVE.w / 2.0 < OUTPUT.h NATIVE.h / 2.0 < * OUTPUT.w NATIVE.w / 1.2 > OUTPUT.h NATIVE.h / 1.2 > * *\n//!WIDTH OUTPUT.w\n//!HEIGHT OUTPUT.h\n\nvec4 hook() {\n\treturn HOOKED_tex(HOOKED_pos);\n}\n"
  },
  {
    "path": "app/src/main/assets/shaders/Anime4K_AutoDownscalePre_x4.glsl",
    "content": "// This is free and unencumbered software released into the public domain.\n\n// Anyone is free to copy, modify, publish, use, compile, sell, or\n// distribute this software, either in source code form or as a compiled\n// binary, for any purpose, commercial or non-commercial, and by any\n// means.\n\n// In jurisdictions that recognize copyright laws, the author or authors\n// of this software dedicate any and all copyright interest in the\n// software to the public domain. We make this dedication for the benefit\n// of the public at large and to the detriment of our heirs and\n// successors. We intend this dedication to be an overt act of\n// relinquishment in perpetuity of all present and future rights to this\n// software under copyright law.\n\n// THE SOFTWARE IS PROVIDED \"AS IS\", WITHOUT WARRANTY OF ANY KIND,\n// EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF\n// MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.\n// IN NO EVENT SHALL THE AUTHORS BE LIABLE FOR ANY CLAIM, DAMAGES OR\n// OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE,\n// ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR\n// OTHER DEALINGS IN THE SOFTWARE.\n\n// For more information, please refer to <https://unlicense.org>\n\n//!DESC Anime4K-v3.2-AutoDownscalePre-x4\n//!HOOK MAIN\n//!BIND HOOKED\n//!BIND NATIVE\n//!WHEN OUTPUT.w NATIVE.w / 4.0 < OUTPUT.h NATIVE.h / 4.0 < * OUTPUT.w NATIVE.w / 2.4 > OUTPUT.h NATIVE.h / 2.4 > * *\n//!WIDTH OUTPUT.w 2 /\n//!HEIGHT OUTPUT.h 2 /\n\nvec4 hook() {\n\treturn HOOKED_tex(HOOKED_pos);\n}\n"
  },
  {
    "path": "app/src/main/assets/shaders/Anime4K_Clamp_Highlights.glsl",
    "content": "// MIT License\n\n// Copyright (c) 2019-2021 bloc97\n// All rights reserved.\n\n// Permission is hereby granted, free of charge, to any person obtaining a copy\n// of this software and associated documentation files (the \"Software\"), to deal\n// in the Software without restriction, including without limitation the rights\n// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell\n// copies of the Software, and to permit persons to whom the Software is\n// furnished to do so, subject to the following conditions:\n\n// The above copyright notice and this permission notice shall be included in all\n// copies or substantial portions of the Software.\n\n// THE SOFTWARE IS PROVIDED \"AS IS\", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR\n// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,\n// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE\n// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER\n// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,\n// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE\n// SOFTWARE.\n\n//!DESC Anime4K-v4.0-De-Ring-Compute-Statistics\n//!HOOK MAIN\n//!BIND HOOKED\n//!SAVE STATSMAX\n//!COMPONENTS 1\n\n#define KERNELSIZE 5 //Kernel size, must be an positive odd integer.\n#define KERNELHALFSIZE 2 //Half of the kernel size without remainder. Must be equal to trunc(KERNELSIZE/2).\n\nfloat get_luma(vec4 rgba) {\n\treturn dot(vec4(0.299, 0.587, 0.114, 0.0), rgba);\n}\n\nvec4 hook() {\n\n\tfloat gmax = 0.0;\n\t\n\tfor (int i=0; i<KERNELSIZE; i++) {\n\t\tfloat g = get_luma(MAIN_texOff(vec2(i - KERNELHALFSIZE, 0)));\n\t\t\n\t\tgmax = max(g, gmax);\n\t}\n\t\n\treturn vec4(gmax, 0.0, 0.0, 0.0);\n}\n\n//!DESC Anime4K-v4.0-De-Ring-Compute-Statistics\n//!HOOK MAIN\n//!BIND HOOKED\n//!BIND STATSMAX\n//!SAVE STATSMAX\n//!COMPONENTS 1\n\n#define KERNELSIZE 5 //Kernel size, must be an positive odd integer.\n#define KERNELHALFSIZE 2 //Half of the kernel size without remainder. Must be equal to trunc(KERNELSIZE/2).\n\nvec4 hook() {\n\n\tfloat gmax = 0.0;\n\t\n\tfor (int i=0; i<KERNELSIZE; i++) {\n\t\tfloat g = STATSMAX_texOff(vec2(0, i - KERNELHALFSIZE)).x;\n\t\t\n\t\tgmax = max(g, gmax);\n\t}\n\t\n\treturn vec4(gmax, 0.0, 0.0, 0.0);\n}\n\n//!DESC Anime4K-v4.0-De-Ring-Clamp\n//!HOOK PREKERNEL\n//!BIND HOOKED\n//!BIND STATSMAX\n\nfloat get_luma(vec4 rgba) {\n\treturn dot(vec4(0.299, 0.587, 0.114, 0.0), rgba);\n}\n\nvec4 hook() {\n\n\tfloat current_luma = get_luma(HOOKED_tex(HOOKED_pos));\n\tfloat new_luma = min(current_luma, STATSMAX_tex(HOOKED_pos).x);\n\t\n\t//This trick is only possible if the inverse Y->RGB matrix has 1 for every row... (which is the case for BT.709)\n\t//Otherwise we would need to convert RGB to YUV, modify Y then convert back to RGB.\n    return HOOKED_tex(HOOKED_pos) - (current_luma - new_luma); \n}"
  },
  {
    "path": "app/src/main/assets/shaders/Anime4K_Restore_CNN_M.glsl",
    "content": "// MIT License\n\n// Copyright (c) 2019-2021 bloc97\n// All rights reserved.\n\n// Permission is hereby granted, free of charge, to any person obtaining a copy\n// of this software and associated documentation files (the \"Software\"), to deal\n// in the Software without restriction, including without limitation the rights\n// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell\n// copies of the Software, and to permit persons to whom the Software is\n// furnished to do so, subject to the following conditions:\n\n// The above copyright notice and this permission notice shall be included in all\n// copies or substantial portions of the Software.\n\n// THE SOFTWARE IS PROVIDED \"AS IS\", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR\n// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,\n// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE\n// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER\n// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,\n// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE\n// SOFTWARE.\n\n//!DESC Anime4K-v4.0-Restore-CNN-(M)-Conv-4x3x3x3\n//!HOOK MAIN\n//!BIND MAIN\n//!SAVE conv2d_tf\n//!WIDTH MAIN.w\n//!HEIGHT MAIN.h\n//!COMPONENTS 4\n#define go_0(x_off, y_off) (MAIN_texOff(vec2(x_off, y_off)))\nvec4 hook() {\n    vec4 result = mat4(-0.09991986, 0.13782342, -0.031251684, -0.06356843, -0.3437488, 0.05450952, 0.34347802, 0.46335372, 0.08607224, 0.044988394, 0.137179, 0.17976908, 0.0, 0.0, 0.0, 0.0) * go_0(-1.0, -1.0);\n    result += mat4(-0.024212424, -0.09278509, -0.00040907756, 0.34552294, -0.13254678, 0.113105185, 0.005667946, -0.00036919137, -0.06375679, 0.009184115, 0.115518734, -0.115506776, 0.0, 0.0, 0.0, 0.0) * go_0(-1.0, 0.0);\n    result += mat4(-0.14101827, 0.023523493, 0.044094566, -0.019271746, -0.44348842, -0.08818877, -0.4026149, -0.21995795, -0.15880394, -0.013732858, -0.020751135, 0.012719151, 0.0, 0.0, 0.0, 0.0) * go_0(-1.0, 1.0);\n    result += mat4(0.013001821, -0.34503505, 0.39219138, 0.18792126, 0.24760444, -0.016173402, 0.10154511, 0.15453082, -0.058132876, 0.016784398, -0.05808539, -0.11039915, 0.0, 0.0, 0.0, 0.0) * go_0(0.0, -1.0);\n    result += mat4(0.37024534, 0.041440863, -0.3374568, -0.44994286, 0.19555596, 0.20855539, -0.27974075, -0.5372628, 0.21228147, -0.0295346, -0.56700057, 0.030042822, 0.0, 0.0, 0.0, 0.0) * go_0(0.0, 0.0);\n    result += mat4(-0.12940632, 0.057526, 0.090682045, -0.06985033, -0.13704006, -0.047685407, 0.44615674, -0.48056605, -0.06166251, -0.01883519, 0.2032237, -0.113287605, 0.0, 0.0, 0.0, 0.0) * go_0(0.0, 1.0);\n    result += mat4(0.010856669, -0.35820737, 0.16757219, 0.082619876, -0.03967303, 0.038705572, 0.32652855, -0.012030017, 0.015120559, -0.15314877, 0.23442009, 0.09767922, 0.0, 0.0, 0.0, 0.0) * go_0(1.0, -1.0);\n    result += mat4(-0.046272673, -0.17752305, 0.082018286, -0.2512824, 0.58619463, -0.060903464, -0.022793597, 0.077803515, -0.17025311, 0.05136993, 0.029383298, -0.15475409, 0.0, 0.0, 0.0, 0.0) * go_0(1.0, 0.0);\n    result += mat4(-0.11212024, 0.13378005, -0.2027488, 0.08056421, -0.11176219, -0.048429377, -0.08396386, 0.10507829, 0.13326839, 0.0430627, 0.051362377, 0.06482755, 0.0, 0.0, 0.0, 0.0) * go_0(1.0, 1.0);\n    result += vec4(-0.061233472, 0.39222646, 0.029704979, 0.02586828);\n    return result;\n}\n//!DESC Anime4K-v4.0-Restore-CNN-(M)-Conv-4x3x3x8\n//!HOOK MAIN\n//!BIND conv2d_tf\n//!SAVE conv2d_1_tf\n//!WIDTH conv2d_tf.w\n//!HEIGHT conv2d_tf.h\n//!COMPONENTS 4\n#define go_0(x_off, y_off) (max((conv2d_tf_texOff(vec2(x_off, y_off))), 0.0))\n#define go_1(x_off, y_off) (max(-(conv2d_tf_texOff(vec2(x_off, y_off))), 0.0))\nvec4 hook() {\n    vec4 result = mat4(-0.16410656, -0.40521824, 0.13121907, -0.02314597, 0.105412476, -0.060401272, -0.043063477, -0.13933973, 0.12558138, -0.020861467, 0.030370515, 0.13178016, -0.14220351, 0.20736893, 0.003321564, -0.29241714) * go_0(-1.0, -1.0);\n    result += mat4(0.18517321, 0.29162985, -0.26783395, 0.039760686, 0.025527012, -0.067319244, 0.055004176, 0.048916563, 0.12750523, -0.091435954, 0.13818842, 0.36704224, 0.0839921, 0.10186618, -0.17237376, 0.13282418) * go_0(-1.0, 0.0);\n    result += mat4(-0.1657887, 0.0131325135, -0.17222486, 0.091398895, -0.12756164, -0.08437298, -0.29052997, 0.3269337, 0.15870757, -0.013529402, -0.0581753, 0.11802371, 0.07099966, -0.024063632, 0.31834844, -0.11183859) * go_0(-1.0, 1.0);\n    result += mat4(0.46036887, -0.07654623, 0.22923063, 0.17463821, 0.10555414, -0.117430426, 0.12406777, -0.011399492, 0.028316498, 0.13684341, 0.009664087, 0.2022659, 0.04953974, -0.31342217, -0.6103131, -0.13605757) * go_0(0.0, -1.0);\n    result += mat4(0.03406955, -0.39819366, 0.61176, -0.46809456, -0.029321073, 0.46619493, 0.36700186, 0.02288561, 0.11464085, -0.10931452, -0.09154022, 0.07334147, -0.5609916, 0.31826234, -0.011012659, -0.46719545) * go_0(0.0, 0.0);\n    result += mat4(-0.056855045, 0.27037027, -0.09269696, -0.563572, -0.06816116, -0.22986612, 0.08693167, -0.16246101, 0.09954046, -0.05374176, 0.0071916827, -0.1788692, 0.3825241, -0.1609887, 0.055204768, 0.10213068) * go_0(0.0, 1.0);\n    result += mat4(0.0646626, 0.102358796, -0.45055822, 0.20557903, -0.23337309, 0.12633002, -0.19299199, -0.15085731, -0.13473304, 0.053790465, -0.10061193, -0.13393497, -0.04264752, -0.029740738, -0.07865285, 0.20883279) * go_0(1.0, -1.0);\n    result += mat4(0.010471527, -0.033218473, -0.46157447, 0.004866583, 0.23226471, -0.059343327, -0.1439596, 0.13619648, 0.013839963, 0.15930325, 0.043742355, 0.17467323, 0.33772305, 0.40261495, -0.08351293, 0.18129359) * go_0(1.0, 0.0);\n    result += mat4(-0.12493434, -0.1875134, -0.074943796, -0.0031701606, -0.037142616, 0.1667002, 0.16665547, -0.011248127, 0.0071619414, 0.0034872112, 0.120318964, -0.09625579, 0.14917047, -0.16310586, 0.07231737, 0.30447328) * go_0(1.0, 1.0);\n    result += mat4(0.093798615, 0.17074613, -0.08780678, -0.012520207, 0.118534856, 0.027508778, -0.2778478, -0.19509242, -0.34137097, 0.32000312, -0.22027159, 0.337515, 0.16220862, 0.108993016, 0.14070526, 0.12784284) * go_1(-1.0, -1.0);\n    result += mat4(-0.14325632, -0.1467453, -0.27502358, 0.09370837, 0.11821083, -0.012266484, -0.2100548, 0.4707502, -0.06766648, 0.58165014, -0.2512279, -0.33783755, 0.1318925, -0.04346277, 0.15454485, 0.044500057) * go_1(-1.0, 0.0);\n    result += mat4(-0.05683207, 0.0051946463, -0.108000524, 0.10133204, -0.50763863, 0.007308442, 0.8542404, 0.28387356, 0.022709515, 0.294523, -0.3822472, 0.66166407, 0.01404485, 0.031282708, -0.26756814, -0.123147786) * go_1(-1.0, 1.0);\n    result += mat4(-0.36455178, 0.3470555, -0.045303088, -0.03170764, -0.15802494, -0.0019141496, -0.25939587, -0.23875342, 0.130428, 0.03954273, -0.17985536, 0.105145946, 0.15804817, 0.12551713, 0.28371975, -0.085748516) * go_1(0.0, -1.0);\n    result += mat4(0.0060625463, 0.2443924, -0.017692259, -0.20214005, -0.09584515, -0.012805372, -0.13942227, 0.16143198, 0.12942013, 0.41785547, 0.046071563, 0.7030026, 0.10499644, -0.20566013, -0.031321276, 0.27830327) * go_1(0.0, 0.0);\n    result += mat4(-0.081274964, -0.14562319, 0.27200526, -0.20491314, 0.012910989, 0.024201397, 0.04816258, 0.21297328, -0.22015952, -0.44160756, -0.056035373, 0.33824417, -0.31645304, 0.15469243, 0.053187452, -0.20989445) * go_1(0.0, 1.0);\n    result += mat4(-0.046550367, 0.033185404, 0.33337244, 0.12853645, 0.23520172, -0.05909214, 0.0861368, 0.10706329, -0.07058717, -0.11759937, -0.18594047, 0.080006264, -0.055425353, -0.12506317, 0.15729053, -0.0915004) * go_1(1.0, -1.0);\n    result += mat4(0.042516407, 0.14844789, 0.16533111, 0.13502933, -0.0655417, -0.057256397, 0.076713726, -0.23448966, 0.12855926, 0.014219275, 0.051761385, 0.053433083, -0.2446715, -0.4008074, 0.19603717, -0.1796951) * go_1(1.0, 0.0);\n    result += mat4(0.14777803, 0.15524907, 0.043158617, -0.06996876, 0.19210646, -0.2144364, -0.47020787, -0.4207906, -0.18074386, -0.2163903, 0.0030754965, 0.36799973, -0.3837698, -0.0022661497, -0.37276733, -0.28934997) * go_1(1.0, 1.0);\n    result += vec4(-0.018297346, -0.080951825, -0.062163066, -0.08050014);\n    return result;\n}\n//!DESC Anime4K-v4.0-Restore-CNN-(M)-Conv-4x3x3x8\n//!HOOK MAIN\n//!BIND conv2d_1_tf\n//!SAVE conv2d_2_tf\n//!WIDTH conv2d_1_tf.w\n//!HEIGHT conv2d_1_tf.h\n//!COMPONENTS 4\n#define go_0(x_off, y_off) (max((conv2d_1_tf_texOff(vec2(x_off, y_off))), 0.0))\n#define go_1(x_off, y_off) (max(-(conv2d_1_tf_texOff(vec2(x_off, y_off))), 0.0))\nvec4 hook() {\n    vec4 result = mat4(0.31543177, 0.23095237, -0.06692611, -0.5867763, 0.003622504, 0.17948842, -0.14627707, 0.1745016, -0.052964583, -0.15551159, 0.05644786, -0.012665164, 0.13107763, 0.11369179, -0.09452995, -0.11973403) * go_0(-1.0, -1.0);\n    result += mat4(-0.2694661, -0.115382135, 0.3073268, -0.067228466, -0.25511482, -0.13922207, 0.36758214, -0.18821828, -0.022617863, 0.20333402, -0.11125889, 0.3552245, -0.013346653, -0.099095374, -0.25100616, 0.35521755) * go_0(-1.0, 0.0);\n    result += mat4(0.011012409, -0.13675085, 0.25642, -0.34851208, -0.23184675, 0.18012202, 0.57654136, 0.103173524, -0.16461405, 0.038177088, 0.1234096, 0.013202029, -0.19033363, 0.07469178, -0.017948546, 0.15287702) * go_0(-1.0, 1.0);\n    result += mat4(-0.05340533, 0.23797482, 0.20351392, -0.05333351, -0.12181174, -0.23363493, -0.20696607, 0.109941036, -0.11519453, 0.13842066, -0.10687832, 0.29040006, 0.022218632, 0.031238724, 0.2685182, 0.15300068) * go_0(0.0, -1.0);\n    result += mat4(0.22985318, -0.3103802, -0.22916415, 0.25238806, -0.11690287, -0.1947488, 0.118020535, 0.07814263, -0.06335474, -0.007870727, 0.076106325, 0.094677486, -0.16776285, -0.006570437, -0.29589584, 0.41413507) * go_0(0.0, 0.0);\n    result += mat4(0.43607962, -0.36456433, -0.123776875, -0.16634953, -0.091190875, 0.13035081, 0.28627968, 0.27249968, 0.12356344, -0.008616177, 0.09599816, -0.006144557, -0.23490307, 0.3013123, 0.14153156, 0.21837278) * go_0(0.0, 1.0);\n    result += mat4(0.060364585, 0.37860224, 0.039182413, -0.22805426, -0.089910224, -0.06817697, -0.2684275, -0.12528503, 0.036934495, -0.07826616, 0.06559976, -0.08253646, 0.13489649, 0.06237663, 0.126376, 0.21194184) * go_0(1.0, -1.0);\n    result += mat4(-0.12534817, 0.21225189, -0.27818045, -0.3070443, -0.006957577, -0.025105853, 0.12100924, -0.06916452, 0.23081483, 0.1802756, -0.18995638, 0.16603014, -0.2904096, -0.25292823, -0.21834068, 0.13719653) * go_0(1.0, 0.0);\n    result += mat4(0.017209655, 0.10757137, 0.21414296, -0.30885983, 0.10467716, -0.2184891, 0.100061476, -0.1527528, 0.2100472, -0.25768545, -0.22329919, -0.29153427, -0.06983842, -0.103854865, -0.051384352, 0.14629121) * go_0(1.0, 1.0);\n    result += mat4(0.0059623295, -0.26060802, 0.32115817, 0.021025505, 0.09783085, -0.15865178, 0.1473021, -0.24977303, -0.033508282, 0.17480391, -0.091310136, 0.09870876, 0.10504043, -0.06105686, 0.013493489, -0.11278855) * go_1(-1.0, -1.0);\n    result += mat4(0.14875248, -0.14859414, 0.19377062, -0.17456068, 0.101288855, -0.1113682, -0.48944646, 0.1018565, -0.037392337, 0.08539691, 0.1751306, -0.15428723, -0.059375558, 0.027663672, 0.051804014, -0.049813222) * go_1(-1.0, 0.0);\n    result += mat4(0.118846565, -0.19869871, -0.037388258, 0.08456728, -0.11662527, -0.43818352, -0.093285345, 0.038507205, -0.051991668, 0.21008292, 0.10792365, 0.2020924, 0.057021596, 0.09460527, 0.0016551288, -0.0015957063) * go_1(-1.0, 1.0);\n    result += mat4(0.11062174, -0.2639232, -0.060295466, -0.3217331, -0.050545212, 0.30989558, 0.30906132, 0.030323273, 0.028986752, 0.037429404, 0.20855664, -0.19848943, 0.034687653, -0.09599135, -0.06250494, -0.13215867) * go_1(0.0, -1.0);\n    result += mat4(-0.010391146, 0.07657845, 0.44491258, 0.0435906, 0.0075931503, 0.42632654, 0.47022533, 0.34737435, -0.15452717, -0.14613411, -0.45231065, 0.12094409, 0.0067911847, 0.057501152, 0.09876979, 0.044946447) * go_1(0.0, 0.0);\n    result += mat4(-0.15607435, 0.2293058, -0.09520331, 0.012836732, -0.15282455, 0.26437718, -0.1685477, -0.13211122, -0.055801593, -0.016778728, -0.34478986, -0.23228309, 0.12300962, -0.13235827, -0.13987203, -0.16550972) * go_1(0.0, 1.0);\n    result += mat4(0.13161735, -0.09039346, -0.033475474, -0.23686698, 0.1514885, 0.20977421, 0.031431954, -0.0049226107, 0.090661936, 0.15288061, -0.03316583, 0.09646573, -0.32651708, 0.18825398, -0.15777239, 0.17572704) * go_1(1.0, -1.0);\n    result += mat4(0.112157226, -0.08712878, 0.23453182, 0.1043877, -0.14686783, 0.28682423, -0.086443506, 0.059457052, -0.31530112, -0.2700583, -0.06028952, -0.070416875, 0.18053482, 0.16653341, 0.25215197, 0.061915852) * go_1(1.0, 0.0);\n    result += mat4(-0.20122242, 0.076313145, -0.0988483, 0.094337784, -0.35436687, 0.3762327, -0.07809558, 0.3055848, 0.10425242, -0.17087407, 0.030301496, -0.13911743, 0.01630275, 0.24247427, -0.006474477, 0.03842641) * go_1(1.0, 1.0);\n    result += vec4(-0.008952847, -0.0058945753, -0.08097229, 0.020968592);\n    return result;\n}\n//!DESC Anime4K-v4.0-Restore-CNN-(M)-Conv-4x3x3x8\n//!HOOK MAIN\n//!BIND conv2d_2_tf\n//!SAVE conv2d_3_tf\n//!WIDTH conv2d_2_tf.w\n//!HEIGHT conv2d_2_tf.h\n//!COMPONENTS 4\n#define go_0(x_off, y_off) (max((conv2d_2_tf_texOff(vec2(x_off, y_off))), 0.0))\n#define go_1(x_off, y_off) (max(-(conv2d_2_tf_texOff(vec2(x_off, y_off))), 0.0))\nvec4 hook() {\n    vec4 result = mat4(-0.2237721, -0.0064096362, -0.31808427, 0.73477733, 0.015353088, 0.23983319, 0.14967978, -0.34920225, -0.07456269, 0.093151815, -0.14331086, -0.24586205, -0.14183366, 0.06401045, -0.22044073, 0.29932275) * go_0(-1.0, -1.0);\n    result += mat4(-0.07968509, -0.3349146, 0.16529128, 0.08443499, 0.4095855, -0.17120704, 0.17425705, 0.15298946, 0.2981273, 0.2212369, 0.10392389, -0.28775454, -0.065247655, -0.15255849, 0.13094437, 0.18685219) * go_0(-1.0, 0.0);\n    result += mat4(0.015706737, -0.17755036, 0.2622526, 0.112057306, -0.15876788, -0.38466996, -0.33700845, -0.031711742, -0.023320962, -0.3145249, -0.21223734, -0.1314596, -0.1888095, -0.046370104, 0.09000896, -0.0046378844) * go_0(-1.0, 1.0);\n    result += mat4(-0.31127506, 0.31304324, -0.03965752, 0.03649018, -0.029851055, 0.05801377, 0.00040150844, -0.04422069, 0.18019931, 0.14415511, -0.09845236, 0.21895434, -0.013932474, -0.046454947, -0.3403935, -0.006705289) * go_0(0.0, -1.0);\n    result += mat4(-0.34878647, -0.5129283, 0.060250953, -0.16354133, 0.20644619, 0.08732273, -0.24118888, 0.24455065, 0.24449423, 0.44103387, 0.22455928, 0.25738943, -0.26914698, -0.21309987, 0.08386486, 0.021484816) * go_0(0.0, 0.0);\n    result += mat4(-0.057454903, -0.4121922, 0.022661546, 0.37178272, 0.03331408, 0.05044008, 0.04324371, 0.20727943, 0.2432641, 0.076906696, -0.20858039, 0.012439015, -0.19335061, 0.09217451, 0.1968369, -0.19435833) * go_0(0.0, 1.0);\n    result += mat4(-0.16960496, 0.24616167, 0.37977478, 0.14324574, -0.011531225, -0.11312143, -0.18141079, -0.23843932, 0.0086012175, -0.3564491, -0.12639481, 0.009799298, -0.29120612, 0.23756824, 0.18035695, -0.087133996) * go_0(1.0, -1.0);\n    result += mat4(-0.10081239, 0.29191494, 0.10434693, 0.08970636, 0.008997759, 0.104756236, 0.039641086, 0.02323888, -0.11627765, 0.023693223, -0.30801758, -0.120208986, 0.05086147, 0.18498175, 0.15595439, -0.09877306) * go_0(1.0, 0.0);\n    result += mat4(0.101321675, -0.2929976, 0.38810417, 0.5605376, -0.04073937, 0.030110704, -0.18147062, -0.09833952, 0.01927733, 0.15335669, -0.15384074, -0.110595055, -0.054297395, -0.077522054, 0.07918369, -0.068480626) * go_0(1.0, 1.0);\n    result += mat4(0.23263514, -0.11719232, 0.2903209, -0.007503795, -0.020222448, -0.17790157, -0.15600762, -0.08741775, 0.12529704, 0.25548857, -0.04585447, -0.10255033, 0.18350503, -0.29593533, 0.0868933, 0.027004737) * go_1(-1.0, -1.0);\n    result += mat4(-0.14958654, -0.006238835, -0.2928948, 0.1988557, -0.17057803, 0.12524141, 0.13978264, -0.019280292, 0.05967142, -0.07790818, -0.5893818, -0.022845713, -0.08596779, 0.07875358, -0.03316667, -0.4369282) * go_1(-1.0, 0.0);\n    result += mat4(0.19195688, -0.060883682, -0.25897828, 0.07063324, 0.090833396, 0.003422883, 0.109534174, 0.031180874, -0.05017118, 0.022862168, -0.270113, -0.057831235, 0.53920543, -0.10252776, -0.091807485, 0.004294343) * go_1(-1.0, 1.0);\n    result += mat4(-0.18494242, -0.119284816, 0.3821897, 0.07777979, 0.15568028, -0.2854859, -0.22441281, -0.049155876, -0.15292497, 0.21895619, -0.095677756, 0.15210424, 0.001643022, -0.026176987, 0.048463076, -0.4824009) * go_1(0.0, -1.0);\n    result += mat4(0.007215129, 0.17074333, 0.053930074, -0.027014816, -0.17180431, -0.15163863, -0.0012122132, -0.18934256, -0.08294297, -0.24580221, -0.46552867, -0.27923223, 0.4092668, 0.06288688, -0.1602188, -0.0030876845) * go_1(0.0, 0.0);\n    result += mat4(0.111870885, 0.03317145, 0.14155298, 0.20328505, -0.05104131, 0.13979794, 0.018966835, -0.07238511, 0.05493792, -0.14975783, -0.10293237, -0.21985306, 0.49054706, 0.18288186, -0.26925826, 0.35845932) * go_1(0.0, 1.0);\n    result += mat4(0.3747799, -0.096748486, -0.17139742, 0.25289854, -0.17421168, -0.018461818, 0.09747162, 0.01660535, -0.20580359, 0.56189656, 0.17151354, -0.26347768, 0.28350568, -0.21486014, -0.44330928, -0.008981037) * go_1(1.0, -1.0);\n    result += mat4(0.10169985, -0.18244018, 0.04760736, 0.41017643, -0.09468786, -0.024218475, 0.103733875, -0.22540338, 0.10630112, 0.3677178, -0.104170956, 0.057317447, 0.21764882, 0.0789158, -0.22041337, 0.15065216) * go_1(1.0, 0.0);\n    result += mat4(0.11633995, -0.008195114, -0.14501533, 0.07168025, 0.058413275, 0.055995367, 0.09362145, -0.13827963, 0.13760869, 0.040319785, 0.038895044, 0.2675253, -0.087339684, 0.1412073, -0.17166458, -0.2312994) * go_1(1.0, 1.0);\n    result += vec4(-0.059377354, -0.02055341, 0.07234869, -0.015452986);\n    return result;\n}\n//!DESC Anime4K-v4.0-Restore-CNN-(M)-Conv-4x3x3x8\n//!HOOK MAIN\n//!BIND conv2d_3_tf\n//!SAVE conv2d_4_tf\n//!WIDTH conv2d_3_tf.w\n//!HEIGHT conv2d_3_tf.h\n//!COMPONENTS 4\n#define go_0(x_off, y_off) (max((conv2d_3_tf_texOff(vec2(x_off, y_off))), 0.0))\n#define go_1(x_off, y_off) (max(-(conv2d_3_tf_texOff(vec2(x_off, y_off))), 0.0))\nvec4 hook() {\n    vec4 result = mat4(-0.29012984, -0.13150147, 0.31015614, 0.05992291, -0.050289866, 0.14845313, -0.09608898, 0.27913308, 0.060307387, -0.04160452, 0.035932682, -0.08137563, -0.07999419, 0.11818284, -0.27512288, 0.21948813) * go_0(-1.0, -1.0);\n    result += mat4(0.12916058, -0.21759962, -0.33868533, 0.021636661, 0.053470243, 0.1412425, 0.043395396, -0.26751056, -0.01689101, -0.2623835, 0.010809152, 0.062962815, -0.20692012, -0.1677863, -0.23313859, -0.17402615) * go_0(-1.0, 0.0);\n    result += mat4(-0.08204112, -0.23672083, -0.0064437394, -0.13200696, -0.056692924, -0.02708657, 0.12536962, 0.004428919, 0.14137582, 0.15404348, -0.105753876, 0.047957454, 0.15734316, 0.16562423, -0.010160829, -0.06602983) * go_0(-1.0, 1.0);\n    result += mat4(0.025653997, -0.10877775, -0.31258908, 0.18841636, -0.36005193, 0.1816357, -0.34537643, -0.0741087, 0.4663994, 0.0065186517, 0.08109033, 0.2976773, -0.35774228, -0.041366056, -0.37852773, 0.050565656) * go_0(0.0, -1.0);\n    result += mat4(0.04392313, 0.11316681, -0.14421389, 0.17985669, -0.1651274, -0.5656209, -0.124100484, 0.42774054, -0.1153939, 0.16829851, 0.2025612, 0.054007456, -0.06868256, -0.56935954, -0.12227961, 0.17688861) * go_0(0.0, 0.0);\n    result += mat4(0.34041, 0.499, 0.15234196, 0.21353458, -0.2732667, -0.049950935, 0.03550811, -0.21051687, 0.2609023, 0.016438454, -0.29874632, 0.37994128, 0.049288407, -0.31126305, 0.029235512, -0.012256015) * go_0(0.0, 1.0);\n    result += mat4(-0.0046853204, 0.15391374, -0.040689662, 0.20186873, -0.08137621, 0.35905558, 0.23733845, 0.21794793, -0.066420384, 0.029600656, -0.31421044, -0.050773863, -0.06260773, 0.04634221, -0.10948491, -0.045498934) * go_0(1.0, -1.0);\n    result += mat4(-0.082953, -0.025837064, -0.09928303, -0.14300232, 0.275064, 0.07793617, 0.22240888, 0.06637834, -0.4382666, -0.2932182, -0.27243167, -0.14221182, 0.5695728, 0.20719238, 0.5575927, 0.40816882) * go_0(1.0, 0.0);\n    result += mat4(-0.18510929, -0.15052167, 0.25277212, 0.06804461, 0.016387, 0.20310035, 0.2903229, -0.0615877, -0.28987274, -0.11942605, 0.013498961, 0.3184152, 0.29543474, -0.042830903, -0.018111207, -0.13263674) * go_0(1.0, 1.0);\n    result += mat4(0.25749087, 0.0053866603, -0.09391162, -0.06129529, -0.094091184, -0.07419633, 0.0013858611, 0.012000353, -0.062903, -0.0204224, -0.12113313, 0.017942557, -0.073379934, 0.052201986, 0.35864577, 0.023564404) * go_1(-1.0, -1.0);\n    result += mat4(0.100115694, 0.19451359, 0.23252094, 0.19506809, -0.12470779, 0.0027281935, -0.17488572, -0.018721964, -0.15159339, 0.18457152, 0.057712987, -0.08191495, 0.19735703, 0.07326743, -0.28563106, 0.01642815) * go_1(-1.0, 0.0);\n    result += mat4(0.068062514, 0.28356665, 0.07377898, 0.42776972, 0.28725025, -0.13045293, -0.17525704, -0.05885591, -0.16676305, -0.2555945, -0.10078422, -0.053032875, 0.084470876, 0.06460686, 0.13824362, -0.05231353) * go_1(-1.0, 1.0);\n    result += mat4(0.22637829, -0.028969254, 0.1968254, -0.13331996, 0.038017053, -0.008854481, -0.2031639, 0.09237089, -0.3821112, 0.1108527, -0.11029933, -0.24542028, 0.22416145, -0.031492114, -0.19144306, -0.0996271) * go_1(0.0, -1.0);\n    result += mat4(0.10776744, 0.16363445, 0.14656505, -0.3737814, -0.06642015, 0.5616549, -0.008412252, -0.37266847, 0.12506576, -0.15329036, 0.037538245, -0.10810259, 0.01706349, 0.1813702, 0.035651788, -0.012786579) * go_1(0.0, 0.0);\n    result += mat4(-0.4023338, -0.2098614, -0.18285121, -0.02727653, 0.26107362, 0.041306913, -0.036515504, -0.045217298, -0.39958602, -0.21229339, -0.021053292, -0.13427502, 0.36178818, 0.20934913, 0.1500852, 0.2634554) * go_1(0.0, 1.0);\n    result += mat4(0.07794611, -0.25937587, -0.06822529, -0.056336135, 0.094220124, 0.21588847, -0.0455218, -0.10968329, -0.08068449, -0.31366697, 0.07799637, 0.24252681, 0.23963861, 0.13715535, 0.010329345, 0.09094301) * go_1(1.0, -1.0);\n    result += mat4(-0.20975718, -0.12550138, 0.14453574, -0.0020878632, -0.07153068, 0.3249998, -0.056577377, 0.18166828, 0.37204072, 0.17018336, 0.3752895, 0.32178587, 0.2571982, -0.27258632, -0.25971004, -0.40536007) * go_1(1.0, 0.0);\n    result += mat4(-0.3243907, -0.06300621, -0.09398436, -0.19549188, 0.14906861, 0.061537784, -0.055284478, 0.11281728, 0.12964857, 0.09979093, -0.1810159, -0.4104283, 0.05807971, -0.056371246, 0.08072554, 0.18479007) * go_1(1.0, 1.0);\n    result += vec4(-0.048888464, -0.0561434, 0.030690912, -0.030496685);\n    return result;\n}\n//!DESC Anime4K-v4.0-Restore-CNN-(M)-Conv-4x3x3x8\n//!HOOK MAIN\n//!BIND conv2d_4_tf\n//!SAVE conv2d_5_tf\n//!WIDTH conv2d_4_tf.w\n//!HEIGHT conv2d_4_tf.h\n//!COMPONENTS 4\n#define go_0(x_off, y_off) (max((conv2d_4_tf_texOff(vec2(x_off, y_off))), 0.0))\n#define go_1(x_off, y_off) (max(-(conv2d_4_tf_texOff(vec2(x_off, y_off))), 0.0))\nvec4 hook() {\n    vec4 result = mat4(0.15332128, 0.027258258, 0.14900503, -0.15982795, 0.17021236, -0.51046044, -0.15287271, -0.058167327, 0.51826185, -0.34817994, 0.004513167, 0.05395769, 0.1990321, -0.049979225, 0.11391989, -0.16062729) * go_0(-1.0, -1.0);\n    result += mat4(0.033682905, 0.019728886, 0.19931756, 0.17381927, 0.2585768, -0.2124572, -0.014632459, 0.39779893, -0.1146207, -0.2396625, 0.08960277, 0.38345298, 0.25497693, 0.11692859, -0.14207517, 0.12667973) * go_0(-1.0, 0.0);\n    result += mat4(-0.14911255, 0.08910706, 0.16136818, 0.03914566, 0.24204038, -0.03607149, -0.4571109, 0.10802461, -0.0021356856, 0.00885878, 0.22297303, 0.2367231, 0.045177583, 0.11120606, -0.009971904, -0.059262395) * go_0(-1.0, 1.0);\n    result += mat4(0.24565999, -0.2261384, 0.47373205, 0.024613412, -0.10923052, 0.039027315, -0.42707404, -0.3783373, 0.3544573, -0.5468578, -0.27599156, -0.09455918, 0.18760219, -0.19082001, 0.030565469, 0.20589156) * go_0(0.0, -1.0);\n    result += mat4(0.1973198, -0.03433863, 0.059960485, 0.045642868, 0.1819595, -0.14460869, 0.1286175, 0.2067575, -0.042632047, -0.11842967, -0.11224446, -0.18764776, -0.19563004, 0.027425969, 0.24056377, 0.5949649) * go_0(0.0, 0.0);\n    result += mat4(0.055027682, 0.16331595, -0.2608588, 0.12545955, 0.4588985, 0.03642909, 0.22187738, 0.45190734, -0.001210133, -0.057651415, -0.061199043, 0.11935476, -0.049561135, 0.27509886, 0.13778673, -0.124914035) * go_0(0.0, 1.0);\n    result += mat4(-0.02257459, 0.27705106, 0.044165276, -0.26521233, 0.05982374, -0.2824302, 0.3171142, 0.08430561, -0.10155528, 0.16182268, -0.09183147, -0.19447176, 0.3295707, -0.50616395, -0.036964044, 0.23166709) * go_0(1.0, -1.0);\n    result += mat4(-0.0232342, 0.07299799, -0.18038079, -0.13672702, -0.108305976, 0.15024792, -0.19531927, 0.0870979, -0.26488534, 0.19481428, 0.10737945, -0.14573483, -0.33094683, 0.24155116, -0.09850332, 0.2797003) * go_0(1.0, 0.0);\n    result += mat4(-0.24089853, 0.19506595, 0.4799156, -0.058313113, 0.36212957, -0.44844806, 0.23864488, 0.15477742, -0.07795971, -0.0033861927, -0.11216164, 0.033454563, -0.25893036, 0.23793478, -0.15769425, -0.00033481256) * go_0(1.0, 1.0);\n    result += mat4(0.05772507, -0.1640253, -0.13499664, -0.20460358, -0.024399966, 0.14966168, -0.090857334, -0.039677754, 0.00036956606, -0.24236615, -0.053542696, -0.0049544116, 0.026651502, 0.39019194, -0.2742246, -0.061242323) * go_1(-1.0, -1.0);\n    result += mat4(-0.016323274, -0.036179908, 0.029965919, 0.11151491, -0.00016685206, -0.29573023, 0.17996423, -0.20145437, 0.1324275, -0.18442132, -0.24618152, 0.061780427, -0.02770517, 0.28452995, 0.39804098, -0.1174389) * go_1(-1.0, 0.0);\n    result += mat4(-0.025068847, -0.053328387, -0.27053785, 0.26866457, -0.09866204, 0.057677213, 0.01850112, -0.18014707, -0.13319959, -0.14411181, -0.26355243, -0.022209354, -0.05062645, -0.036771543, 0.13294417, -0.18458557) * go_1(-1.0, 1.0);\n    result += mat4(-0.046194963, 0.038230438, -0.08993043, -0.07236354, 0.11031123, -0.16504908, -0.09517036, -0.16459833, -0.5279925, 0.12686682, -0.05726125, 0.055361677, 0.31593755, 0.027328093, 0.001839602, 0.30581662) * go_1(0.0, -1.0);\n    result += mat4(0.08608678, 0.03168437, 0.007713377, -0.26140293, -0.1268983, 0.13395861, -0.069848835, -0.24080403, 0.018839337, -0.049821075, -0.21461345, -0.14168301, -0.0872339, 0.47096667, 0.022512507, 0.14860632) * go_1(0.0, 0.0);\n    result += mat4(0.06293673, 0.22462969, 0.045494985, 0.021673543, 0.18227446, -0.2956555, 0.08010543, -0.01919729, -0.012190269, 0.241983, -0.046537094, -0.40094566, -0.3853647, 0.1081711, -0.16926058, 0.16138376) * go_1(0.0, 1.0);\n    result += mat4(-0.14854589, -0.17625804, -0.10849075, 0.221543, 0.099971965, 0.13901573, 0.29464146, 0.020068526, 0.054358527, -0.10351705, -0.0062914286, 0.24127026, -0.16914125, 0.12729423, -0.18377453, -0.6452375) * go_1(1.0, -1.0);\n    result += mat4(0.12603393, -0.10986093, 0.2314103, 0.16915044, -0.13619255, -0.09349073, 0.20594226, -0.34507084, 0.19077192, 0.052500796, 0.07185645, 0.029082738, -0.015576321, 0.08254907, -0.5501743, -0.38495848) * go_1(1.0, 0.0);\n    result += mat4(0.09300796, -0.079218306, 0.46825135, -0.08735625, 0.06321122, 0.16234867, 0.042932414, -0.013057422, 0.09697148, 0.23457524, 0.19417483, -0.16804664, 0.18379296, 0.17770062, -0.050235, -0.059676602) * go_1(1.0, 1.0);\n    result += vec4(0.011169491, 0.032399546, 0.138099, 0.023857072);\n    return result;\n}\n//!DESC Anime4K-v4.0-Restore-CNN-(M)-Conv-4x3x3x8\n//!HOOK MAIN\n//!BIND conv2d_5_tf\n//!SAVE conv2d_6_tf\n//!WIDTH conv2d_5_tf.w\n//!HEIGHT conv2d_5_tf.h\n//!COMPONENTS 4\n#define go_0(x_off, y_off) (max((conv2d_5_tf_texOff(vec2(x_off, y_off))), 0.0))\n#define go_1(x_off, y_off) (max(-(conv2d_5_tf_texOff(vec2(x_off, y_off))), 0.0))\nvec4 hook() {\n    vec4 result = mat4(-0.22753362, -0.08612073, 0.33140692, 0.08699529, -0.18788953, -0.056579117, -0.12905197, -0.06694621, 0.054559365, 0.15031597, -0.13430363, 0.021646025, 0.14884405, -0.0694291, 0.26149413, 0.11270503) * go_0(-1.0, -1.0);\n    result += mat4(0.17876762, -0.09637848, 0.11285323, 0.2004893, 0.1317187, -0.036162686, 0.17958368, -0.069625, 0.28760737, -0.12505141, 0.12760694, 0.047717955, -0.16811855, -0.16340709, 0.13278298, -0.08403954) * go_0(-1.0, 0.0);\n    result += mat4(-0.21917523, 0.079711854, -0.28642535, 0.2822416, 0.03001489, -0.014772918, -0.3487396, 0.10597145, -0.013841082, 0.17034237, 0.10810282, -0.08089695, -0.22184245, -0.59067357, 0.44113398, 0.13045649) * go_0(-1.0, 1.0);\n    result += mat4(-0.29906932, 0.013923749, 0.2031124, -0.11846688, -0.13953634, 0.08003455, -0.10164494, -0.21218559, 0.10563715, 0.31033117, -0.075903505, 0.047310907, -0.37824214, -0.14506383, 0.11866701, -0.21384487) * go_0(0.0, -1.0);\n    result += mat4(-0.1353849, 0.19258606, 0.063908584, -0.2043788, 0.27244982, 0.1665306, -0.29357895, -0.22441709, 0.18514316, -0.17840464, 0.20986097, 0.14351055, -0.057732623, 0.42166704, -0.23182064, -0.4957248) * go_0(0.0, 0.0);\n    result += mat4(-0.34830126, 0.109066755, -0.28285867, -0.048280068, -0.12290918, 0.04291651, -0.047484186, -0.03702595, 0.23047262, 0.09398974, 0.022467108, 0.08271034, 0.3066665, -0.54077, 0.057771873, 0.23194093) * go_0(0.0, 1.0);\n    result += mat4(-0.17731948, -0.3175927, 0.1452728, 0.09396786, -0.16433562, -0.01833653, -0.22345604, -0.04161193, -0.14827462, 0.18544114, -0.15544125, -0.06179007, 0.16989979, -0.20985202, 0.16391534, -0.09447268) * go_0(1.0, -1.0);\n    result += mat4(-0.053878862, -0.21034616, 0.023831524, 0.19772215, 0.31647214, 0.0126534775, -0.19130844, -0.049282108, -0.21446131, 0.067189045, 0.09117449, -0.25548774, 0.12109098, 0.22009392, -0.3924665, -0.13340388) * go_0(1.0, 0.0);\n    result += mat4(-0.16096684, -0.18495405, 0.10410178, 0.0015673033, -0.00183498, -0.044303037, -0.062745355, -0.090802394, 0.043269135, 0.06924481, -0.21367405, -0.14619029, 0.11555763, -0.20292862, 0.5799557, 0.14739846) * go_0(1.0, 1.0);\n    result += mat4(-0.21030277, -0.09578802, 0.013482288, -0.21484336, 0.12995781, 0.40431052, -0.3347856, -0.18183486, 0.15550353, -0.04402301, 0.4603779, 0.14874357, -0.07694621, -0.053523075, -0.19607326, -0.10850742) * go_1(-1.0, -1.0);\n    result += mat4(-0.2347211, 0.2697403, -0.0634794, -0.17925987, 0.17231455, 0.24999185, -0.5208536, -0.10491828, -0.233575, 0.52950364, 0.0038063182, -0.1380038, 0.022935199, 0.19369157, 0.14586553, 0.1938704) * go_1(-1.0, 0.0);\n    result += mat4(-0.10245223, 0.34150192, 0.25862157, -0.20165509, 0.5597771, 0.114510864, -0.122526556, -0.04010975, 0.1704679, -0.23335956, -0.16771887, -0.03783455, -0.056995615, 0.24153493, -0.08082429, -0.24210933) * go_1(-1.0, 1.0);\n    result += mat4(-0.103466526, 0.15278348, -0.30526164, -0.080755696, 0.103505425, 0.15862796, 0.14696524, -0.008358076, -0.09180311, -0.12505089, 0.28052542, -0.13551563, 0.07528779, -0.09636086, -0.10369617, 0.23656134) * go_1(0.0, -1.0);\n    result += mat4(-0.25752836, 0.099439755, -0.30716348, 0.035077725, 0.023509016, 0.23106368, 0.05277125, 0.34910464, 0.088015385, 0.26995596, 0.1390645, -0.40671825, 0.18096298, -0.100688554, 0.5492049, 0.2482101) * go_1(0.0, 0.0);\n    result += mat4(0.41411775, -0.107200556, -0.13813478, 0.13768874, 0.27137747, 0.06313619, -0.08522967, 0.03218302, -0.03166121, -0.3415683, -0.52242, -0.1741813, -0.36956537, 0.179129, -0.09742935, -0.11696616) * go_1(0.0, 1.0);\n    result += mat4(-0.07975504, 0.17964838, 0.37122533, 0.16064765, 0.14309953, 0.29473078, 0.0926391, -0.22333665, 0.34612748, -0.3387473, 0.0077308523, -0.07239449, 0.18522519, -0.21297298, 0.11493978, 0.16117814) * go_1(1.0, -1.0);\n    result += mat4(-0.17402779, 0.10023144, 0.11712206, 0.031971734, 0.18713303, 0.08736295, 0.013007052, -0.06943139, -0.20102951, -0.010721135, -0.2562522, 0.34877458, -0.13732676, -0.40258047, 0.25824392, 0.15720639) * go_1(1.0, 0.0);\n    result += mat4(0.044494305, 0.3296108, 0.0017603852, 0.09362289, 0.38839245, 0.40015858, -0.13395199, -0.044521853, -0.56266373, 0.251378, 0.5005789, -0.13106057, -0.18491416, -0.046887, 0.067797676, -0.14694957) * go_1(1.0, 1.0);\n    result += vec4(0.013687534, -0.08185164, -0.04755438, 0.290178);\n    return result;\n}\n//!DESC Anime4K-v4.0-Restore-CNN-(M)-Conv-3x1x1x56\n//!HOOK MAIN\n//!BIND MAIN\n//!BIND conv2d_tf\n//!BIND conv2d_1_tf\n//!BIND conv2d_2_tf\n//!BIND conv2d_3_tf\n//!BIND conv2d_4_tf\n//!BIND conv2d_5_tf\n//!BIND conv2d_6_tf\n//!SAVE MAIN\n//!WIDTH conv2d_tf.w\n//!HEIGHT conv2d_tf.h\n#define g_0 (max((conv2d_tf_tex(conv2d_tf_pos)), 0.0))\n#define g_1 (max(-(conv2d_tf_tex(conv2d_tf_pos)), 0.0))\n#define g_2 (max((conv2d_1_tf_tex(conv2d_1_tf_pos)), 0.0))\n#define g_3 (max(-(conv2d_1_tf_tex(conv2d_1_tf_pos)), 0.0))\n#define g_4 (max((conv2d_2_tf_tex(conv2d_2_tf_pos)), 0.0))\n#define g_5 (max(-(conv2d_2_tf_tex(conv2d_2_tf_pos)), 0.0))\n#define g_6 (max((conv2d_3_tf_tex(conv2d_3_tf_pos)), 0.0))\n#define g_7 (max(-(conv2d_3_tf_tex(conv2d_3_tf_pos)), 0.0))\n#define g_8 (max((conv2d_4_tf_tex(conv2d_4_tf_pos)), 0.0))\n#define g_9 (max(-(conv2d_4_tf_tex(conv2d_4_tf_pos)), 0.0))\n#define g_10 (max((conv2d_5_tf_tex(conv2d_5_tf_pos)), 0.0))\n#define g_11 (max(-(conv2d_5_tf_tex(conv2d_5_tf_pos)), 0.0))\n#define g_12 (max((conv2d_6_tf_tex(conv2d_6_tf_pos)), 0.0))\n#define g_13 (max(-(conv2d_6_tf_tex(conv2d_6_tf_pos)), 0.0))\nvec4 hook() {\n    vec4 result = mat4(-0.08837163, -0.065234736, -0.034704313, 0.0, 0.021405501, 0.013663729, 0.019249594, 0.0, 0.05328863, 0.03580334, 0.046457592, 0.0, -0.12216048, 0.022547891, 0.016400825, 0.0) * g_0;\n    result += mat4(0.061996464, 0.05631466, 0.06808407, 0.0, -0.005013109, -0.0044589997, -0.032367796, 0.0, 0.016481603, 0.13721058, 0.14924648, 0.0, 0.020035887, -0.07250003, -0.08034037, 0.0) * g_1;\n    result += mat4(0.24078514, 0.081361525, 0.053420708, 0.0, -0.009353794, -0.051077116, -0.058007747, 0.0, -0.14071098, 0.01035966, 0.005308949, 0.0, -0.1489842, -0.06711817, -0.05552926, 0.0) * g_2;\n    result += mat4(-0.13002375, 0.012733757, 0.017821986, 0.0, 0.17767483, 0.20204604, 0.1751779, 0.0, 0.12804912, 0.07381453, 0.05655911, 0.0, 0.17044514, 0.07301451, 0.06523978, 0.0) * g_3;\n    result += mat4(-0.1170986, -0.05130371, -0.027939914, 0.0, -0.16645707, -0.121526904, -0.09471366, 0.0, -0.04143118, 0.026693767, 0.034615446, 0.0, -0.084318705, -0.064990036, -0.054324172, 0.0) * g_4;\n    result += mat4(0.12094524, 0.09518409, 0.07387219, 0.0, 0.062216382, 0.053228356, 0.031372335, 0.0, 0.072797105, 0.026258165, 0.009804673, 0.0, 0.120719045, 0.073281154, 0.056623302, 0.0) * g_5;\n    result += mat4(-0.11141495, -0.11566289, -0.10398725, 0.0, -0.0651895, -0.06820691, -0.054204144, 0.0, -0.032746475, -0.008849683, -0.007610222, 0.0, -0.024655705, -0.048778858, -0.041144755, 0.0) * g_6;\n    result += mat4(0.058090195, 0.07538767, 0.059722915, 0.0, 0.044788487, 0.04212742, 0.027502589, 0.0, 0.04892866, 0.015416752, 0.008312418, 0.0, -0.011864114, -0.0074752793, -0.0060824654, 0.0) * g_7;\n    result += mat4(0.043446552, 0.061971307, 0.05758086, 0.0, -0.06379154, -0.053758245, -0.047204215, 0.0, 0.016307736, 0.03423424, 0.030179083, 0.0, 0.041445345, 0.03843772, 0.033059113, 0.0) * g_8;\n    result += mat4(-0.003803544, 0.0008906116, -0.00059585314, 0.0, 0.102071285, 0.11485224, 0.10007254, 0.0, -0.074306004, -0.08803551, -0.07972321, 0.0, -0.030704215, -0.021514274, -0.009049376, 0.0) * g_9;\n    result += mat4(0.0066058086, 0.0011408008, 0.0016199006, 0.0, -0.03916473, -0.042929266, -0.04018418, 0.0, -0.03153446, -0.039413508, -0.034767237, 0.0, 0.113516055, 0.12577052, 0.113335624, 0.0) * g_10;\n    result += mat4(0.02655948, 0.041905303, 0.03861737, 0.0, 0.048471425, 0.049788587, 0.050447535, 0.0, 0.12092813, 0.13564217, 0.12613249, 0.0, -0.0023508538, 0.0012828974, 0.0028730957, 0.0) * g_11;\n    result += mat4(0.0084758485, 0.008800083, 0.008206044, 0.0, -0.056123603, -0.06610845, -0.060320783, 0.0, -0.081793964, -0.101638645, -0.096699014, 0.0, -0.04402356, -0.04177539, -0.03829645, 0.0) * g_12;\n    result += mat4(0.10676299, 0.118409514, 0.10618478, 0.0, -0.05880252, -0.06488367, -0.06432695, 0.0, 0.019221924, 0.017602798, 0.017413978, 0.0, -0.07512528, -0.080483615, -0.066218294, 0.0) * g_13;\n    result += vec4(-0.010478934, -0.008364784, -0.010246552, 0.0);\n    return result + MAIN_tex(MAIN_pos);\n}\n"
  },
  {
    "path": "app/src/main/assets/shaders/Anime4K_Restore_CNN_S.glsl",
    "content": "// MIT License\n\n// Copyright (c) 2019-2021 bloc97\n// All rights reserved.\n\n// Permission is hereby granted, free of charge, to any person obtaining a copy\n// of this software and associated documentation files (the \"Software\"), to deal\n// in the Software without restriction, including without limitation the rights\n// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell\n// copies of the Software, and to permit persons to whom the Software is\n// furnished to do so, subject to the following conditions:\n\n// The above copyright notice and this permission notice shall be included in all\n// copies or substantial portions of the Software.\n\n// THE SOFTWARE IS PROVIDED \"AS IS\", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR\n// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,\n// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE\n// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER\n// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,\n// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE\n// SOFTWARE.\n\n//!DESC Anime4K-v4.0-Restore-CNN-(S)-Conv-4x3x3x3\n//!HOOK MAIN\n//!BIND MAIN\n//!SAVE conv2d_tf\n//!WIDTH MAIN.w\n//!HEIGHT MAIN.h\n//!COMPONENTS 4\n#define go_0(x_off, y_off) (MAIN_texOff(vec2(x_off, y_off)))\nvec4 hook() {\n    vec4 result = mat4(-0.19288683, -0.21397883, 0.111997396, -0.04791413, -0.26682988, -0.06144587, -0.03601853, -0.16693151, 0.038494494, -0.16651472, 0.147657, -0.083003886, 0.0, 0.0, 0.0, 0.0) * go_0(-1.0, -1.0);\n    result += mat4(-0.14286195, 0.08746566, -0.40107322, 0.12390977, -0.33392772, -0.18703035, -0.21326795, 0.04780781, -0.15155545, -0.0010025925, -0.1554875, -0.10676251, 0.0, 0.0, 0.0, 0.0) * go_0(-1.0, 0.0);\n    result += mat4(0.28095165, 0.022872915, -0.21342312, -0.29982176, 0.025937587, -0.055012174, -0.33779636, 0.0015666655, 0.076416336, 0.06656033, -0.1557806, 0.1078894, 0.0, 0.0, 0.0, 0.0) * go_0(-1.0, 1.0);\n    result += mat4(-0.31584853, 0.07527119, 0.30713862, -0.34014285, -0.50103146, -0.07217874, 0.512807, -0.09597398, -0.32097813, -0.051580857, -0.022466356, 0.01148551, 0.0, 0.0, 0.0, 0.0) * go_0(0.0, -1.0);\n    result += mat4(-0.026032459, -0.04193211, 0.37703893, -0.031916667, -0.27421117, 1.0906446, -0.049654085, -0.19814016, 0.07819544, 0.06003738, 0.1405805, -0.0064135445, 0.0, 0.0, 0.0, 0.0) * go_0(0.0, 0.0);\n    result += mat4(0.041450135, 0.11319654, -0.23237701, 0.08443178, 0.53344345, 0.30857387, -0.057264958, -0.1575803, 0.2325609, -0.027797326, -0.04544767, -0.18720597, 0.0, 0.0, 0.0, 0.0) * go_0(0.0, 1.0);\n    result += mat4(0.2531829, -0.074966915, -0.27800754, -0.3146097, 0.20126024, -0.5380133, -0.15082566, -0.19021043, 0.29951036, 0.17123336, -0.01681872, -0.12574998, 0.0, 0.0, 0.0, 0.0) * go_0(1.0, -1.0);\n    result += mat4(0.25203633, 0.19882993, 0.14906439, 0.13593598, 0.40712556, 0.084902965, 0.42969635, 0.2961132, -0.057267334, -0.030388135, 8.8084314e-05, 0.0210724, 0.0, 0.0, 0.0, 0.0) * go_0(1.0, 0.0);\n    result += mat4(-0.13459359, -0.12199573, 0.12591946, 0.24736497, 0.2033463, -0.09388599, -0.094370656, 0.1071285, -0.18479438, -0.066625565, 0.08279283, 0.20130983, 0.0, 0.0, 0.0, 0.0) * go_0(1.0, 1.0);\n    result += vec4(-0.011108127, -0.07481861, 0.07640154, 0.4964964);\n    return result;\n}\n//!DESC Anime4K-v4.0-Restore-CNN-(S)-Conv-4x3x3x8\n//!HOOK MAIN\n//!BIND conv2d_tf\n//!SAVE conv2d_1_tf\n//!WIDTH conv2d_tf.w\n//!HEIGHT conv2d_tf.h\n//!COMPONENTS 4\n#define go_0(x_off, y_off) (max((conv2d_tf_texOff(vec2(x_off, y_off))), 0.0))\n#define go_1(x_off, y_off) (max(-(conv2d_tf_texOff(vec2(x_off, y_off))), 0.0))\nvec4 hook() {\n    vec4 result = mat4(-0.056432575, 0.0028165397, -0.026325442, -0.14802271, 0.16885762, -0.062179096, -0.2332292, 0.17513658, -0.08011296, 0.02947316, 0.014771492, -0.17946689, 0.026012989, -0.09823925, 0.036625937, -0.06924322) * go_0(-1.0, -1.0);\n    result += mat4(-0.13571467, 0.09831142, 0.12911566, 0.06305893, -0.07188695, -0.20161287, 0.3858435, -0.21069056, -0.12294444, -0.1404628, -0.022659872, 0.23008968, 0.10969853, 0.17640765, 0.39796907, 0.20413099) * go_0(-1.0, 0.0);\n    result += mat4(-0.0061665224, 0.055102807, -0.0059629944, -0.021429887, 0.061626043, 0.16898955, -0.21215646, 0.16510476, 0.2238265, 0.19429931, 0.09874656, 0.06828208, -0.122404456, -0.00026717107, -0.28203064, -0.29979932) * go_0(-1.0, 1.0);\n    result += mat4(-0.22735378, 0.14538136, 0.11549746, 0.194148, -0.09841722, -0.0661309, 0.348576, -0.017375294, -0.044078812, 0.1298332, 0.04793373, -0.30687734, 0.08353025, 0.083519086, 0.10766399, 0.31796935) * go_0(0.0, -1.0);\n    result += mat4(0.048365135, -0.17566709, -0.33212858, -0.052667376, -0.26443407, -0.010216014, 0.1573303, 0.05725314, 0.08140953, -0.09664591, 0.076109104, -0.026773714, 0.07732627, 0.10188082, -0.28266954, -0.16230233) * go_0(0.0, 0.0);\n    result += mat4(0.29931107, 0.117944, -0.10414009, 0.12795551, 0.12576093, 0.17082554, -0.15803693, 0.13430743, -0.025801308, -0.10797019, 0.0721032, 0.2825884, -0.11025257, 0.12798019, 0.081827976, -0.050441865) * go_0(0.0, 1.0);\n    result += mat4(-0.11827391, 0.08306765, -0.3430314, 0.07898041, -0.023839617, -0.019507334, 0.23176382, -0.40992323, 0.09411734, 0.38415068, -0.25845516, -0.29984522, 0.1470966, -0.0684779, -0.07071314, -0.026773235) * go_0(1.0, -1.0);\n    result += mat4(0.19091596, 0.082110435, -0.5266589, -0.1744098, -0.015838385, -0.046316292, 0.023171103, -0.03731331, 0.2642396, 0.31824252, -0.041754793, -0.09525519, -0.14696182, 0.052168854, 0.039857205, -0.027555354) * go_0(1.0, 0.0);\n    result += mat4(0.15207373, 0.09845733, 0.0142631065, 0.096375965, 0.06089903, 0.17902578, -0.42391995, 0.22475442, 0.016356342, -0.06277531, -0.12173141, -0.18635495, -0.0013459618, 0.15725887, 0.019310836, 0.20293565) * go_0(1.0, 1.0);\n    result += mat4(-0.18395247, 0.30672902, 0.09034339, 0.1821889, -0.0419004, -0.2169228, -0.14052129, 0.11006559, 0.1709272, 0.51062274, 0.13758625, -0.2242552, -0.030382963, 0.3357568, -0.26491287, 0.02501938) * go_1(-1.0, -1.0);\n    result += mat4(0.040511727, 0.12523083, -0.27318433, 0.08388512, 0.25354835, 0.3404216, -0.2632471, -0.17784123, 0.2732347, 0.4468553, 0.084667034, -0.1856242, 0.034099877, -0.00954992, -0.32751867, -0.062207516) * go_1(-1.0, 0.0);\n    result += mat4(0.17564747, 0.11645554, -0.16362113, 0.105654195, -0.2762563, -0.1413764, 0.23264363, -0.14000498, 0.095402054, 0.0715738, -0.19346157, -0.028285999, 0.009799127, 0.04059529, 0.19688335, 0.1282381) * go_1(-1.0, 1.0);\n    result += mat4(0.23575781, -0.11446148, -0.20504695, 0.035568226, 0.36890212, -0.85968876, -0.18545328, 0.33796397, -0.30916876, -0.10445518, -0.3046253, 0.33271998, -0.06263589, -0.2160114, -0.16383372, -0.31173357) * go_1(0.0, -1.0);\n    result += mat4(0.20469664, 0.4039374, -0.070057206, 0.030353077, 0.39843914, -0.15490077, -0.24476516, 0.38238233, -0.21809858, 0.23496576, -0.051794037, 0.033664484, -0.14411364, -0.2515329, 0.124655396, -0.05818785) * go_1(0.0, 0.0);\n    result += mat4(-0.09065731, -0.16787091, 0.013269188, 0.23687351, -0.41504318, -0.048163068, 0.31760025, -0.33648986, 0.29752317, 0.2926866, 0.14408836, -0.33382463, -0.15873958, -0.121961035, 0.11797893, 0.09000567) * go_1(0.0, 1.0);\n    result += mat4(0.13356976, 0.013763947, 0.012169505, -0.109594524, 0.03417223, 0.7031121, 0.65146804, 0.5250268, -0.50132495, -0.419648, 0.2940041, 0.83051753, -0.17595838, 0.1633008, -0.018587278, 0.079596795) * go_1(1.0, -1.0);\n    result += mat4(0.07570128, -0.1581438, 0.03904949, 0.14890033, -0.054611947, 0.17469402, -0.44252598, 0.036181703, -0.4981031, -0.37507218, -0.18466389, 0.2645845, 0.25189674, -0.025896115, 0.034307647, -0.020462232) * go_1(1.0, 0.0);\n    result += mat4(-0.11645865, 0.02296537, 0.040909223, 0.015069485, 0.062284566, -0.22526766, 0.09241534, -0.32623053, 0.18208642, 0.3954284, 0.2884468, -0.25137675, -0.037232924, -0.10185309, -0.17956531, 0.018966453) * go_1(1.0, 1.0);\n    result += vec4(-0.16371979, -0.024620198, -0.035754893, 0.04176776);\n    return result;\n}\n//!DESC Anime4K-v4.0-Restore-CNN-(S)-Conv-4x3x3x8\n//!HOOK MAIN\n//!BIND conv2d_1_tf\n//!SAVE conv2d_2_tf\n//!WIDTH conv2d_1_tf.w\n//!HEIGHT conv2d_1_tf.h\n//!COMPONENTS 4\n#define go_0(x_off, y_off) (max((conv2d_1_tf_texOff(vec2(x_off, y_off))), 0.0))\n#define go_1(x_off, y_off) (max(-(conv2d_1_tf_texOff(vec2(x_off, y_off))), 0.0))\nvec4 hook() {\n    vec4 result = mat4(0.01921286, -0.26684764, -0.12663573, 0.31641877, -0.25313398, 0.12264074, 0.58750325, -0.14084283, 0.5837018, -0.042300556, -0.20435576, -0.009954825, 0.060783498, 0.05540401, 0.2205112, -0.06578902) * go_0(-1.0, -1.0);\n    result += mat4(-0.21930243, -0.03774968, 0.22615197, 0.18338196, 0.011201461, -0.271034, 0.00573116, -0.12248194, 0.47990513, 0.2982416, -0.1087603, -0.050099242, -0.07620939, -0.07148229, 0.03691984, -0.16796488) * go_0(-1.0, 0.0);\n    result += mat4(-0.14962853, -0.053769328, 0.02387081, 0.22002189, 0.052237745, -0.26160842, -0.08603077, 0.012542448, 0.08119985, 0.075785555, -0.33437458, -0.43373227, -0.13206963, -0.08759176, -0.03288923, -0.09799959) * go_0(-1.0, 1.0);\n    result += mat4(-0.1305593, -0.5974288, 0.06058367, 0.08406488, 0.013692483, 0.06646377, 0.16469325, 0.08990975, 0.42217395, -0.11289523, -0.06165009, 0.48556912, -0.15702641, -0.19922857, -0.0035429662, -0.0022089656) * go_0(0.0, -1.0);\n    result += mat4(-0.1964807, 0.038099788, 0.21587034, 0.039734077, -0.07063389, 0.11604167, -0.24558097, -0.08900199, -0.7684516, -0.1037487, -0.09380674, 0.33144563, -0.16653742, 0.0028585843, -0.33774406, -0.0528696) * go_0(0.0, 0.0);\n    result += mat4(-0.27298656, -0.05665099, 0.09661685, 0.19780266, 0.1025106, -0.22055034, -0.21218458, -0.040628925, 0.0095010325, 0.13118382, -0.42582452, -0.22197723, 0.21006055, -0.06189587, -0.15285942, -0.09526762) * go_0(0.0, 1.0);\n    result += mat4(-0.14494462, -0.046788953, 0.065877035, 0.09911713, 0.35096622, 0.16682479, 0.028363144, 0.36037162, 0.29413632, 0.28212717, -0.025364442, -0.3406269, 0.047262143, -0.11892685, -0.008032766, 0.29743317) * go_0(1.0, -1.0);\n    result += mat4(-0.15191558, -0.36980554, 0.14555687, 0.0043930537, -0.012661432, 0.15737776, -0.115250416, 0.10324491, 0.24491951, -0.15575431, -0.27802598, 0.21959937, 0.18063772, 0.4455559, -0.09693302, 0.33382267) * go_0(1.0, 0.0);\n    result += mat4(0.2717801, 0.13452889, 0.14105384, 0.16324317, -0.40111846, 0.1154301, -0.0076733204, -0.09697362, 0.44306824, -0.02831414, -0.2153124, -0.12075326, 0.060776163, 0.30347148, -0.0036976219, -0.12070682) * go_0(1.0, 1.0);\n    result += mat4(-0.39780128, -0.29875937, -0.12952097, 0.080333896, 0.07520163, 0.021689568, -0.23121156, -0.038140096, -0.1593877, 0.017156163, -0.06038025, 0.009244022, -0.13917233, 0.30957314, 0.243109, -0.104947075) * go_1(-1.0, -1.0);\n    result += mat4(-0.07965157, 0.06776501, -0.13288979, 0.005851189, -0.08768168, -0.03689969, 0.12034646, 0.22441491, 0.14453568, -0.17648841, -0.3378289, -0.018329712, 0.11722939, -0.34161824, 0.08424494, -0.01400687) * go_1(-1.0, 0.0);\n    result += mat4(0.08153887, 0.07222914, -0.14663404, -0.038526025, -0.07385973, 0.18440577, 0.35890242, 0.17084727, 0.26345527, 0.15280858, -0.007446105, -0.024403179, -0.30336383, -0.22978698, 0.11612946, -0.23614909) * go_1(-1.0, 1.0);\n    result += mat4(-0.07447396, 0.09023449, -0.13798, -0.086943336, -0.30787337, 0.15087669, 0.14418626, -0.03371195, 0.048989657, -0.13075387, -0.13458036, -0.059836224, 0.06495196, 0.269715, 0.3674355, 0.38956037) * go_1(0.0, -1.0);\n    result += mat4(0.34981915, -0.048779126, 0.31717536, 0.38080826, -0.20149232, -0.82969636, -0.10167862, 0.6382858, 0.25976858, 0.4370118, -0.04724865, -0.10014156, 0.19380626, -0.080370255, 0.09578106, -0.035166856) * go_1(0.0, 0.0);\n    result += mat4(-0.026443917, 0.4132611, 0.01822534, 0.12742202, -0.26652107, -0.2996705, 0.30905882, 0.07989903, 0.38249823, 0.21486135, 0.025314959, -0.14717339, -0.13344015, -0.32088286, -0.2833883, -0.30973712) * go_1(0.0, 1.0);\n    result += mat4(0.021517841, 0.006556378, 0.2025686, -0.12044382, -0.38583103, -0.0027515136, -0.06556736, -0.097090125, 0.04676486, -0.11954886, -0.051612873, 0.07831412, -0.18823163, -0.16542958, 0.04245155, 0.6437998) * go_1(1.0, -1.0);\n    result += mat4(-0.39475346, -0.2936861, 0.26768062, -0.28151843, 0.21935691, 0.2101108, -0.15455097, 0.19548604, 0.09188909, -0.020147726, 0.103328265, -0.12574542, -0.34167948, 0.07523185, -0.17669058, 0.62446547) * go_1(1.0, 0.0);\n    result += mat4(-0.37661025, -0.29630858, 0.05451026, 0.1611643, 0.14079669, -0.2170294, -0.038716137, 0.13514164, -0.21235192, -0.07860726, -0.005749412, 0.025625167, -0.13297133, 0.33012658, -0.27434957, -0.18416783) * go_1(1.0, 1.0);\n    result += vec4(-0.0036821906, -0.050239526, -0.01355402, 0.00048220603);\n    return result;\n}\n//!DESC Anime4K-v4.0-Restore-CNN-(S)-Conv-3x3x3x8\n//!HOOK MAIN\n//!BIND MAIN\n//!BIND conv2d_2_tf\n//!SAVE MAIN\n//!WIDTH conv2d_2_tf.w\n//!HEIGHT conv2d_2_tf.h\n#define go_0(x_off, y_off) (max((conv2d_2_tf_texOff(vec2(x_off, y_off))), 0.0))\n#define go_1(x_off, y_off) (max(-(conv2d_2_tf_texOff(vec2(x_off, y_off))), 0.0))\nvec4 hook() {\n    vec4 result = mat4(0.15873, 0.17989138, 0.14648493, 0.0, -0.017379675, -0.017363746, -0.019855022, 0.0, 0.009670625, 0.0070157526, 0.0075994316, 0.0, 0.025388412, 0.027231036, 0.024052646, 0.0) * go_0(-1.0, -1.0);\n    result += mat4(0.048195973, 0.041760173, 0.037366055, 0.0, -0.115950756, -0.12887983, -0.12535639, 0.0, 0.032125086, 0.03397254, 0.032950625, 0.0, 0.01223746, 0.020822672, 0.0161561, 0.0) * go_0(-1.0, 0.0);\n    result += mat4(0.0890567, 0.094453335, 0.09014035, 0.0, 0.016081346, 0.017434116, 0.020783134, 0.0, -0.011775135, -0.010094134, -0.018522855, 0.0, 0.072103254, 0.07940666, 0.065876864, 0.0) * go_0(-1.0, 1.0);\n    result += mat4(-0.04841196, -0.06963968, -0.056574684, 0.0, 0.10912542, 0.11813441, 0.10643838, 0.0, -0.013013885, -0.01562045, -0.013802797, 0.0, 0.037505716, 0.04352026, 0.04645123, 0.0) * go_0(0.0, -1.0);\n    result += mat4(-0.3472869, -0.36243078, -0.33530185, 0.0, 0.23654196, 0.2305048, 0.22150646, 0.0, -0.045226905, -0.041799217, -0.042511635, 0.0, -0.10267792, -0.1123385, -0.10845448, 0.0) * go_0(0.0, 0.0);\n    result += mat4(0.011987401, 0.012285043, 0.007813165, 0.0, -0.15911353, -0.17523928, -0.1535267, 0.0, 0.15675929, 0.16531634, 0.15948962, 0.0, -0.09240023, -0.09513292, -0.084187366, 0.0) * go_0(0.0, 1.0);\n    result += mat4(0.069052905, 0.07278333, 0.0756627, 0.0, -0.012180326, -0.018794727, -0.031050753, 0.0, -0.044663202, -0.04362803, -0.038904265, 0.0, -0.008540197, -0.011201734, -0.01556625, 0.0) * go_0(1.0, -1.0);\n    result += mat4(-0.08261173, -0.09042543, -0.07589266, 0.0, 0.043515377, 0.045066774, 0.04037769, 0.0, -0.06262993, -0.07469342, -0.058593787, 0.0, 0.026696987, 0.028740842, 0.037405368, 0.0) * go_0(1.0, 0.0);\n    result += mat4(0.07975598, 0.09597654, 0.08997132, 0.0, -0.07844719, -0.07880916, -0.06835411, 0.0, 0.05668995, 0.050163813, 0.053357534, 0.0, -0.020040333, -0.019867316, -0.01907621, 0.0) * go_0(1.0, 1.0);\n    result += mat4(-0.017078733, -0.017393313, -0.008266595, 0.0, -0.0033478448, -0.0027439648, -0.0042334674, 0.0, -0.06354017, -0.062058125, -0.04652064, 0.0, -0.010787706, -0.0062706997, -0.007573461, 0.0) * go_1(-1.0, -1.0);\n    result += mat4(-0.019895451, -0.016341688, -0.008712399, 0.0, 0.026231976, 0.023955572, 0.0216376, 0.0, -0.061950512, -0.05481285, -0.05261985, 0.0, -0.018804235, -0.016235247, -0.0131616965, 0.0) * go_1(-1.0, 0.0);\n    result += mat4(-0.055628926, -0.063315354, -0.057192408, 0.0, -0.0256364, -0.028660972, -0.02937357, 0.0, -0.017604912, -0.020851422, -0.016070362, 0.0, -0.0870202, -0.0832279, -0.07525406, 0.0) * go_1(-1.0, 1.0);\n    result += mat4(0.062738225, 0.07106593, 0.061644047, 0.0, -0.06068257, -0.06983662, -0.066070385, 0.0, 0.024919355, 0.03227179, 0.028569462, 0.0, -0.07866227, -0.098967604, -0.092128105, 0.0) * go_1(0.0, -1.0);\n    result += mat4(0.040397774, 0.047241107, 0.03962998, 0.0, -0.09112752, -0.10057507, -0.09301817, 0.0, 0.10833967, 0.101835825, 0.10027467, 0.0, 0.27189335, 0.27433604, 0.26781923, 0.0) * go_1(0.0, 0.0);\n    result += mat4(-0.044211388, -0.042373534, -0.03658007, 0.0, 0.113148406, 0.12423258, 0.107804194, 0.0, -0.17081551, -0.18562958, -0.17475435, 0.0, 0.09636739, 0.10763415, 0.093332425, 0.0) * go_1(0.0, 1.0);\n    result += mat4(-0.03798545, -0.047811143, -0.050768293, 0.0, 0.018775463, 0.026812987, 0.03452908, 0.0, 0.0055677597, 0.0039081173, -0.0017878668, 0.0, -0.10728597, -0.12618187, -0.109045394, 0.0) * go_1(1.0, -1.0);\n    result += mat4(0.06359783, 0.064184755, 0.04934199, 0.0, -0.009819327, -0.006616115, -0.007431496, 0.0, 0.025055679, 0.024787048, 0.017360551, 0.0, -0.047140837, -0.061695747, -0.06440822, 0.0) * go_1(1.0, 0.0);\n    result += mat4(0.060199022, 0.06482763, 0.059514645, 0.0, 0.026998974, 0.028776823, 0.024897143, 0.0, 0.17968474, 0.19337215, 0.16760105, 0.0, 0.0075838566, 0.010503482, 0.011993149, 0.0) * go_1(1.0, 1.0);\n    result += vec4(-0.0052927984, -0.0060193934, -0.0048643993, 0.0);\n    return result + MAIN_tex(MAIN_pos);\n}\n"
  },
  {
    "path": "app/src/main/assets/shaders/Anime4K_Restore_CNN_VL.glsl",
    "content": "// MIT License\n\n// Copyright (c) 2019-2021 bloc97\n// All rights reserved.\n\n// Permission is hereby granted, free of charge, to any person obtaining a copy\n// of this software and associated documentation files (the \"Software\"), to deal\n// in the Software without restriction, including without limitation the rights\n// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell\n// copies of the Software, and to permit persons to whom the Software is\n// furnished to do so, subject to the following conditions:\n\n// The above copyright notice and this permission notice shall be included in all\n// copies or substantial portions of the Software.\n\n// THE SOFTWARE IS PROVIDED \"AS IS\", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR\n// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,\n// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE\n// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER\n// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,\n// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE\n// SOFTWARE.\n\n//!DESC Anime4K-v4.0-Restore-CNN-(VL)-Conv-4x3x3x3\n//!HOOK MAIN\n//!BIND MAIN\n//!SAVE conv2d_tf\n//!WIDTH MAIN.w\n//!HEIGHT MAIN.h\n//!COMPONENTS 4\n#define go_0(x_off, y_off) (MAIN_texOff(vec2(x_off, y_off)))\nvec4 hook() {\n    vec4 result = mat4(0.1690102, -0.2560719, 0.39658326, -0.3679659, -0.27616683, -0.35619372, -0.3748396, 0.08430813, -0.29574734, -0.31511316, -0.09773105, 0.13616018, 0.0, 0.0, 0.0, 0.0) * go_0(-1.0, -1.0);\n    result += mat4(-0.1326393, -0.259433, 0.025070239, 0.58914864, -0.036478516, 0.30723435, 0.007458902, 0.012962684, 0.2493056, 0.13007334, -0.08448256, -0.38414413, 0.0, 0.0, 0.0, 0.0) * go_0(-1.0, 0.0);\n    result += mat4(-0.11539356, 0.35253766, 0.26143202, 0.2760807, -0.09371543, -0.028165473, -0.028452158, -0.27050856, 0.06718067, -0.0056619495, -0.17654495, 0.17288211, 0.0, 0.0, 0.0, 0.0) * go_0(-1.0, 1.0);\n    result += mat4(-0.16145481, -0.3204927, -0.54317135, 0.11830119, 0.49315026, 0.12008072, 0.50857407, -0.30382085, 0.25807253, 0.020755528, 0.29388228, 0.106109895, 0.0, 0.0, 0.0, 0.0) * go_0(0.0, -1.0);\n    result += mat4(-0.22728722, 0.50484747, -0.07904469, 0.33114597, 0.50306976, -0.22760947, 0.14773269, 0.17628263, 0.14788547, -0.08223464, -0.10880935, -0.3151985, 0.0, 0.0, 0.0, 0.0) * go_0(0.0, 0.0);\n    result += mat4(0.3414351, 0.057279214, -0.14419858, 0.09761111, -0.11794496, 0.021717256, -0.22750235, 0.13986664, -0.38932344, 0.28996095, 0.3773904, 0.13175532, 0.0, 0.0, 0.0, 0.0) * go_0(0.0, 1.0);\n    result += mat4(0.1376552, -0.19587159, -0.35147396, -0.097646296, 0.1686707, -0.14385861, 0.031198, 0.12383533, -0.23089902, 0.08707301, 0.3362293, -0.100579016, 0.0, 0.0, 0.0, 0.0) * go_0(1.0, -1.0);\n    result += mat4(-0.056774966, 0.047585852, -0.36395878, -0.20211312, 0.4077735, 0.12631284, 0.39813092, -0.033365678, 0.2307249, -0.09131807, 0.20823865, 0.31084216, 0.0, 0.0, 0.0, 0.0) * go_0(1.0, 0.0);\n    result += mat4(-0.12456089, 0.09755632, 0.31490886, -0.06579996, -0.13386595, 0.07564795, -0.26605195, -0.075180635, -0.11182657, 0.06757017, -0.14351276, -0.16828312, 0.0, 0.0, 0.0, 0.0) * go_0(1.0, 1.0);\n    result += vec4(-0.046043985, 0.055581126, -0.08791638, -0.13022089);\n    return result;\n}\n//!DESC Anime4K-v4.0-Restore-CNN-(VL)-Conv-4x3x3x3\n//!HOOK MAIN\n//!BIND MAIN\n//!SAVE conv2d_tf1\n//!WIDTH MAIN.w\n//!HEIGHT MAIN.h\n//!COMPONENTS 4\n#define go_0(x_off, y_off) (MAIN_texOff(vec2(x_off, y_off)))\nvec4 hook() {\n    vec4 result = mat4(-0.15485518, -0.29363206, -0.22610365, -0.14291525, -0.45240572, -0.18319772, -0.12209436, 0.15031648, 0.09878383, 0.06711082, 0.25763842, -0.084633484, 0.0, 0.0, 0.0, 0.0) * go_0(-1.0, -1.0);\n    result += mat4(-0.10204406, 0.16167697, 0.22371867, -0.37947702, -0.24476196, -0.038824454, 0.060157117, 0.15764871, -0.08072927, -0.2210841, -0.31835055, 0.009979876, 0.0, 0.0, 0.0, 0.0) * go_0(-1.0, 0.0);\n    result += mat4(0.20506924, 0.21132155, -0.0922578, -0.07430473, 0.14529926, 0.20549752, 0.0077948375, 0.13246094, -0.32353187, 0.21074104, 0.092629515, 0.17590871, 0.0, 0.0, 0.0, 0.0) * go_0(-1.0, 1.0);\n    result += mat4(0.04125819, -0.44050243, 0.23729716, 0.3218237, 0.12943116, -0.011674174, 0.10390632, 0.027775545, -0.20308031, -0.16904089, -0.2121676, -0.022515794, 0.0, 0.0, 0.0, 0.0) * go_0(0.0, -1.0);\n    result += mat4(0.09664124, 0.20127031, 0.60345304, 0.16697013, 0.23093723, -0.38116834, 0.109695725, 0.0007595324, 0.4092646, 0.009624758, 0.11229678, 0.25326383, 0.0, 0.0, 0.0, 0.0) * go_0(0.0, 0.0);\n    result += mat4(0.014879592, 0.19204311, 0.07102085, -0.7312604, 0.34860876, 0.3429918, -0.027331594, 0.27636307, 0.1342437, 0.107820466, -0.12645108, 0.21081445, 0.0, 0.0, 0.0, 0.0) * go_0(0.0, 1.0);\n    result += mat4(-0.12687613, -0.09247973, -0.25973785, 0.4350873, -0.18987224, 0.028678741, -0.0903819, -0.63974863, 0.205591, 0.11308998, 0.18458389, -0.4149041, 0.0, 0.0, 0.0, 0.0) * go_0(1.0, -1.0);\n    result += mat4(0.34691808, -0.025498383, 0.3428986, 0.21663484, 0.23404741, -0.1725327, -0.0036315925, -0.13299675, -0.1873967, 0.031331502, -0.08785591, -0.0013278709, 0.0, 0.0, 0.0, 0.0) * go_0(1.0, 0.0);\n    result += mat4(-0.35846514, 0.048703704, -0.104165934, 0.16529736, -0.15378916, 0.26030356, -0.07134151, 0.03692383, -0.15807101, -0.18885155, 0.044707954, -0.11444462, 0.0, 0.0, 0.0, 0.0) * go_0(1.0, 1.0);\n    result += vec4(-0.0022791293, -0.024132347, -0.57621074, 0.028573977);\n    return result;\n}\n//!DESC Anime4K-v4.0-Restore-CNN-(VL)-Conv-4x3x3x16\n//!HOOK MAIN\n//!BIND conv2d_tf\n//!BIND conv2d_tf1\n//!SAVE conv2d_1_tf\n//!WIDTH conv2d_tf.w\n//!HEIGHT conv2d_tf.h\n//!COMPONENTS 4\n#define go_0(x_off, y_off) (max((conv2d_tf_texOff(vec2(x_off, y_off))), 0.0))\n#define go_1(x_off, y_off) (max((conv2d_tf1_texOff(vec2(x_off, y_off))), 0.0))\n#define go_2(x_off, y_off) (max(-(conv2d_tf_texOff(vec2(x_off, y_off))), 0.0))\n#define go_3(x_off, y_off) (max(-(conv2d_tf1_texOff(vec2(x_off, y_off))), 0.0))\nvec4 hook() {\n    vec4 result = mat4(0.010346764, 0.07230188, -0.24734616, -0.09937907, 0.02228549, -0.19550583, -0.019540425, -0.1037373, 0.033996485, -0.075554, -0.20228972, 0.07090153, -0.09194035, -0.058972966, 0.1768268, 0.27517542) * go_0(-1.0, -1.0);\n    result += mat4(0.020078976, 0.12433655, -0.1620775, 0.036401592, 0.079748705, 0.11660013, 0.17917652, -0.017513236, -0.18936846, 0.24478136, -0.45726213, -0.045004416, -0.08295188, 0.067733586, -0.080548316, 0.2744211) * go_0(-1.0, 0.0);\n    result += mat4(0.024916803, 0.27562472, 0.043771956, -0.012240604, 0.0786355, 0.042651594, 0.16049327, -0.14577515, -0.032735053, 0.17658092, 0.16382934, -0.02337374, 0.11551492, 0.056343183, -0.17930213, 0.14259394) * go_0(-1.0, 1.0);\n    result += mat4(0.20010485, 0.06747722, -0.19026905, 0.11013709, 0.13062745, -0.044626113, -0.0062261797, 0.2189639, 0.1403497, -0.022713251, -0.19452858, -0.010305412, -0.06407589, 0.09836748, 0.025805516, 0.23430973) * go_0(0.0, -1.0);\n    result += mat4(-0.14664203, 0.034910418, 0.024714258, -0.066872925, -0.15717538, -0.14179383, -0.14091893, 0.05859166, 0.18919097, -0.18544437, -0.09068573, -0.08615929, -0.051434122, 0.2170678, 0.18409058, -0.17461225) * go_0(0.0, 0.0);\n    result += mat4(-0.11354446, 0.10745854, 0.2682663, 0.05949201, -0.10695986, 0.1407851, -0.03551388, 0.10691649, -0.17148238, -0.38287184, 0.2074456, 0.11828914, 0.048535194, 0.1464864, -0.18169662, -0.14074169) * go_0(0.0, 1.0);\n    result += mat4(0.22160622, -0.1513045, -0.053284165, 0.033202525, 0.15574448, -0.043640967, -0.0093824165, -0.0019965349, -0.097964935, -0.08289824, 0.08239996, 0.07868361, 0.05731752, -0.20441617, -0.013016076, -0.253108) * go_0(1.0, -1.0);\n    result += mat4(-0.031249097, -0.2272863, 0.23573665, 0.03357689, 0.011395065, -0.10885564, -0.06287508, -0.031719524, 0.10331069, 0.17560169, 0.18303394, 0.022961004, -0.17011635, -0.24371737, 0.10678694, -0.3222825) * go_0(1.0, 0.0);\n    result += mat4(-0.1275465, -0.08844758, 0.10994917, -0.00910273, 0.09393154, 0.03894992, 0.14367905, -0.11811715, -0.09077633, -0.015776094, 0.27427456, -0.13283503, 0.18724327, -0.08139094, 0.04933602, -0.051852766) * go_0(1.0, 1.0);\n    result += mat4(-0.06764611, -0.27426586, 0.12045272, 0.09410856, -0.14258035, 0.11802992, -0.09093882, 0.0022018093, 0.4590643, 0.046258576, -0.07827223, 0.448011, -0.103631735, -0.016930219, -0.15421398, 0.11045997) * go_1(-1.0, -1.0);\n    result += mat4(-0.17295076, 0.00151352, 0.14938255, 0.08336512, -0.07496541, -0.07561223, -0.0846474, 0.14979269, -0.09142163, 0.23925088, -0.015199518, -0.37749895, -0.20636298, -0.022585187, -0.20371509, 0.0745308) * go_1(-1.0, 0.0);\n    result += mat4(0.06458832, -0.009722021, -0.123604394, 0.06548835, -0.3039139, -0.022024399, 0.05297587, -0.0626883, 0.23556642, 0.1516464, -0.07004877, -0.1845364, -0.05918428, 0.19158973, -0.14983447, 0.030489758) * go_1(-1.0, 1.0);\n    result += mat4(0.36604697, 0.17516142, -0.10853731, -0.22694224, -0.107650936, 0.23013335, 0.094055794, -0.17047717, -0.3006048, -0.08621717, -0.18815655, -0.03570218, 0.09676118, -0.017718751, 0.059138596, 0.073388465) * go_1(0.0, -1.0);\n    result += mat4(-0.12791575, 0.101956226, 0.13091874, -0.046373338, 0.04955811, -0.04030444, 0.13869923, -0.046699073, -0.42611042, -0.7173929, 0.052184317, 0.6178025, -0.02929954, -0.07638965, -0.15000828, 0.030710017) * go_1(0.0, 0.0);\n    result += mat4(0.057806686, 0.20842272, -0.20148766, 0.006666912, 0.13356528, -0.45265228, -0.07354092, 0.21447696, 0.019552143, -0.13645506, 0.14643854, -0.0071413796, -0.15487236, -0.002250615, 0.30622452, 0.0033902125) * go_1(0.0, 1.0);\n    result += mat4(0.06896002, 0.24397352, -0.06479052, 0.20676947, -0.24259068, 0.055320013, -0.09032122, -0.11222854, -0.08982342, -0.114818625, -0.06399291, -0.3024516, -0.06302166, -0.1925528, 0.03458982, 0.028828239) * go_1(1.0, -1.0);\n    result += mat4(0.09764086, 0.09599894, -0.0073313303, 0.14418933, -0.045712367, 0.12657364, 0.04620374, -0.069778584, 0.30047333, -0.012418192, 0.15516461, -0.18087754, 0.08178273, 0.14262857, -0.01741533, -0.12509112) * go_1(1.0, 0.0);\n    result += mat4(0.04697884, -0.1506804, 0.031823065, 0.13397239, -0.18396698, 0.10681781, -0.29586303, -0.0039136545, 0.17560847, -0.12486726, -0.018646788, -0.20688744, -0.030614454, -0.0527634, 0.23593572, -0.10542146) * go_1(1.0, 1.0);\n    result += mat4(-0.19182229, -0.32615846, 0.26283535, -0.1371942, -0.071202695, 0.12056063, -0.11450658, -0.27711076, -0.42096004, 0.0014352369, 0.1559669, -0.14464542, -0.17973948, 0.079166576, -0.12501791, -0.20623216) * go_2(-1.0, -1.0);\n    result += mat4(0.12469872, 0.32190827, -0.059510354, 0.1393449, -0.12845798, -0.019571869, -0.22630808, -0.14031963, 0.36072046, 0.05858427, 0.19278921, 0.121090546, -0.067538865, -0.018770566, 0.14318037, -0.15561756) * go_2(-1.0, 0.0);\n    result += mat4(0.024663208, 0.21110268, -0.016415706, 0.060093414, -0.03739678, -0.107412934, -0.077527136, 0.30331334, 0.17196326, -0.15512557, -0.09499732, -0.15748607, -0.16680105, -0.015185634, 0.16114107, -0.21288376) * go_2(-1.0, 1.0);\n    result += mat4(-0.17739037, -0.1190967, 0.13191372, -0.2527187, -0.14992718, -0.30511454, 0.19145966, 0.002194003, -0.12888977, 0.19152176, 0.27528167, 0.099714965, 0.12865707, -0.12051514, -0.055013947, 0.26231763) * go_2(0.0, -1.0);\n    result += mat4(0.46433613, -0.11708138, -0.20157282, 0.32022122, 0.079468675, 0.029407484, 0.2559102, -0.15651533, 0.08644574, -0.09747344, -0.07528584, 0.17354868, 0.19167562, -0.17698488, -0.09896657, 0.17093097) * go_2(0.0, 0.0);\n    result += mat4(0.20283653, -0.33680332, 0.2282385, 0.18832158, 0.20866042, 0.00076752366, 0.16471444, -0.21548858, 0.16193539, 0.17141372, 0.03140222, 0.03913644, -0.030161971, 0.00014570929, 0.08993654, -0.064823024) * go_2(0.0, 1.0);\n    result += mat4(-0.3075755, 0.19942546, 0.015526995, -0.120868504, -0.254515, -0.07791228, 0.03271691, 0.11794217, 0.11258601, 0.045204375, -0.061196107, -0.115958795, 0.3861869, 0.048215542, 0.07016682, -0.009975758) * go_2(1.0, -1.0);\n    result += mat4(-0.07623697, 0.16094944, -0.02283455, 0.14112763, -0.051149167, 0.20429814, 0.011314802, 0.18914083, -0.24240434, -0.08784008, -0.16763984, -0.08492233, 0.31062725, -0.11925119, -0.33195966, 0.2060798) * go_2(1.0, 0.0);\n    result += mat4(-0.016709225, -0.14472668, -0.3677625, -0.09832719, 0.030297454, -0.05775362, -0.1401375, 0.08119674, -0.01795042, 0.05183797, -0.24320887, 0.066842034, -0.22245285, -0.02740993, 0.06316751, 0.053399116) * go_2(1.0, 1.0);\n    result += mat4(-0.039214406, -0.08876633, 0.045552462, 0.19226661, 0.1355001, -0.13942362, 0.17398876, 0.2914014, -0.191809, 0.037143208, 0.013333581, -0.16632195, 0.113767646, -0.106692605, 0.1589787, 0.030107044) * go_3(-1.0, -1.0);\n    result += mat4(0.21997562, 0.13855208, -0.05783191, -0.033682413, -0.010961168, 0.10524961, 0.02177416, 0.18289444, 0.043692037, 0.07853899, -0.039936125, -0.1004449, 0.04494073, -0.020680292, 0.17578089, -0.106598996) * go_3(-1.0, 0.0);\n    result += mat4(0.026852835, -0.16037546, 0.11278316, 0.12656097, -0.006857894, -0.03400118, -0.051564034, 0.00085412664, -0.37556714, -0.05279987, 0.029383834, -0.14246808, -0.056380164, -0.002399925, 0.16025752, 0.036324855) * go_3(-1.0, 1.0);\n    result += mat4(0.022709966, 0.046350412, 0.03390721, 0.02810572, -0.14394265, 0.04215361, -0.3206118, 0.15034916, -0.0028448137, 0.1682989, -0.042686664, 0.020543462, -0.2786501, -0.007482015, -0.040313292, -0.20745736) * go_3(0.0, -1.0);\n    result += mat4(0.05417556, 0.18728684, -0.046121832, -0.27939513, 0.05907976, -0.09191223, -0.16625418, -0.26038164, 0.39956605, -0.052594025, -0.0596556, 0.29517552, -0.015181923, -0.0763375, 0.25131205, 0.13038464) * go_3(0.0, 0.0);\n    result += mat4(-0.036903054, -0.0066989153, -0.062650286, 0.05614359, -0.0064960583, 0.028512698, -0.10906273, -0.010047654, 0.23030473, 0.049983572, 0.10439064, 0.26643834, 0.05041243, 0.09185424, -0.32352915, 0.11295159) * go_3(0.0, 1.0);\n    result += mat4(0.09724027, -0.34962535, 0.06586686, 0.016635379, 0.13831381, 0.01707076, -0.04690347, 0.022350075, 0.018352794, 0.022000022, 0.070613205, 0.117735535, -0.025971051, 0.18832101, -0.09643588, -0.08512127) * go_3(1.0, -1.0);\n    result += mat4(-0.17324433, 0.06810613, -0.057295907, -0.05115964, -0.101570815, 0.12491774, 0.08762367, -0.005862404, -0.05342927, -0.031942457, -0.039624047, -0.04298937, -0.1303138, -0.11869282, -0.024832053, 0.070463404) * go_3(1.0, 0.0);\n    result += mat4(-0.010514842, 0.1376259, -0.11750346, -0.03786737, 0.03459249, 0.015408171, -0.031430878, -0.060825355, -0.072958425, -0.0037895301, 0.041686177, -0.12352204, -0.06261361, 0.054514423, -0.34072715, 0.13860728) * go_3(1.0, 1.0);\n    result += vec4(0.018166734, -0.11002478, -0.05554318, -0.0988193);\n    return result;\n}\n//!DESC Anime4K-v4.0-Restore-CNN-(VL)-Conv-4x3x3x16\n//!HOOK MAIN\n//!BIND conv2d_tf\n//!BIND conv2d_tf1\n//!SAVE conv2d_1_tf1\n//!WIDTH conv2d_tf.w\n//!HEIGHT conv2d_tf.h\n//!COMPONENTS 4\n#define go_0(x_off, y_off) (max((conv2d_tf_texOff(vec2(x_off, y_off))), 0.0))\n#define go_1(x_off, y_off) (max((conv2d_tf1_texOff(vec2(x_off, y_off))), 0.0))\n#define go_2(x_off, y_off) (max(-(conv2d_tf_texOff(vec2(x_off, y_off))), 0.0))\n#define go_3(x_off, y_off) (max(-(conv2d_tf1_texOff(vec2(x_off, y_off))), 0.0))\nvec4 hook() {\n    vec4 result = mat4(-0.040142782, 0.0288423, 0.07569487, -0.01490842, 0.14402796, -0.13682005, 0.027765118, 0.03907358, 0.07117706, 0.058157545, -0.23862502, -0.057674367, -0.19220531, 0.0147159435, -0.18028538, 0.0963821) * go_0(-1.0, -1.0);\n    result += mat4(-0.1676744, -0.11937339, 0.12137117, 0.07119485, 0.14148116, -0.043578617, -0.029261118, -0.0016938087, -0.057269357, -0.080076694, 0.12193026, 0.07326153, -0.056278303, -0.01630716, -0.03792076, 0.1483611) * go_0(-1.0, 0.0);\n    result += mat4(-0.3021578, 0.011601693, 0.11266048, 0.19086999, -0.0122412145, 0.08431291, 0.11615175, -0.008039614, -0.39987534, 0.07820729, 0.03509667, 0.1963505, -0.08839513, -0.21571854, 0.059425723, -0.06830175) * go_0(-1.0, 1.0);\n    result += mat4(0.23135209, -0.12452708, 0.0943565, 0.0028859286, -0.09836373, 0.10681712, -0.3535964, 0.08457615, 0.045332734, 0.16579892, -0.03809797, -0.021596594, 0.2937497, -0.028294371, 0.046484597, -0.037604347) * go_0(0.0, -1.0);\n    result += mat4(0.072675414, -0.16431206, 0.28952035, 0.0076831076, -0.020242939, 0.029483542, -0.092415355, 0.08673106, 0.12109694, 0.14307201, 0.23134442, 0.11731775, 0.09981601, -0.16968462, 0.037470713, 0.14948717) * go_0(0.0, 0.0);\n    result += mat4(0.0029752052, 0.06526503, 0.1866458, 0.07451277, -0.31836876, 0.17115082, -0.13969697, 0.23844297, -0.03244903, -0.08832665, 0.023691226, -0.18230624, -0.074933805, -0.00044301842, 0.050572682, 0.081511915) * go_0(0.0, 1.0);\n    result += mat4(0.039502528, 0.051221415, -0.13968123, -0.091212444, -0.016925618, 0.15409444, -0.017455677, -0.11653652, 0.03539446, -0.00087720866, -0.12839639, 0.037198763, 0.03674469, -0.26444665, 0.019721227, -0.13013805) * go_0(1.0, -1.0);\n    result += mat4(0.039229527, 0.25667152, 0.0032586441, -0.00718359, 0.1617932, 0.10409968, 0.07182867, -0.09810605, 0.07789241, -0.02014911, 0.025767172, -0.14604759, 0.07175764, 0.32513744, -0.20473222, -0.16266066) * go_0(1.0, 0.0);\n    result += mat4(0.13418433, 0.061813723, -0.13927278, -0.2498272, 0.03468218, 0.29483125, 0.063289374, -0.04726235, 0.1898295, -0.33132064, 0.032045014, 0.02159535, -0.1148363, 0.31306976, 0.06456038, 0.048988886) * go_0(1.0, 1.0);\n    result += mat4(0.07151646, 0.2799246, -0.107190795, -0.16431166, -0.28007045, 0.07206954, 0.06775463, 0.009758042, 0.07032184, -0.20843789, 0.087045245, 0.1360676, -0.25718534, 0.028249472, -0.12614648, 0.009949602) * go_1(-1.0, -1.0);\n    result += mat4(0.020241471, -0.23390484, -0.0083223935, 0.08344701, 0.08222297, 0.12026539, -0.08652223, -0.08228822, -0.039576706, -0.24677879, -0.1157289, 0.2590508, -0.23809408, 0.19911982, -0.116798095, -0.035870325) * go_1(-1.0, 0.0);\n    result += mat4(0.024991842, 0.050509237, -0.024134455, -0.12659028, 0.24089767, 0.122712664, -0.10482493, -0.19403952, -0.19177693, -0.06538376, -0.041478425, 0.32176673, -0.1534002, -0.18680622, 0.06763643, 0.020806564) * go_1(-1.0, 1.0);\n    result += mat4(0.03437814, -0.28067374, 0.2830681, 0.038812317, -0.021698112, -0.120865285, 0.22695538, -0.045419116, -0.030475847, -0.01977341, -0.1265364, -0.3109814, 0.012255813, 0.053917278, -0.018620957, -0.14599285) * go_1(0.0, -1.0);\n    result += mat4(-0.016204128, -0.04093018, 0.054571863, 0.02679643, 0.01756274, -0.057685968, 0.16148666, 0.17370272, -0.11065411, 0.06378157, -0.09331551, 0.22985275, 0.057905316, 0.12323568, 0.07748665, 0.09878629) * go_1(0.0, 0.0);\n    result += mat4(-0.018112244, 0.063234635, -0.013184602, 0.16241394, 0.08877139, 0.02145378, -0.02490027, -0.038920373, 0.13127136, 0.14391647, 0.020553736, 0.14401346, 0.06685973, -0.25398204, 0.10369067, -0.055949755) * go_1(0.0, 1.0);\n    result += mat4(0.07710333, 0.047412727, 0.13813803, 0.18624061, 0.16907091, -0.039532468, 0.06234584, 0.06408178, -0.054543987, -0.045220226, -0.11093376, -0.37399602, 0.20372874, 0.004580967, -0.07742308, 0.017989937) * go_1(1.0, -1.0);\n    result += mat4(0.003485311, -0.08897399, -0.013108594, -0.19473282, -0.27081844, -0.16812073, 0.0052992934, -0.055331517, 0.09446357, 0.019280333, 0.16560757, -0.3230032, 0.043096773, 0.059222896, -0.064184934, -0.059852477) * go_1(1.0, 0.0);\n    result += mat4(0.06794279, -0.034135245, 0.083064295, 0.13506731, 0.13064219, -0.44978833, -0.03513717, 0.08999715, 0.1124541, 0.42208397, -0.0038724816, -0.014332087, -0.13751853, -0.04929869, 0.09134992, -0.17687531) * go_1(1.0, 1.0);\n    result += mat4(0.100909084, -0.0131197255, 0.082274795, -0.2138443, -0.08515947, -0.021058358, 0.10951775, -0.06349191, -0.29129833, -0.029262653, 0.25235432, -0.11748315, 0.121980384, 0.062347785, 0.10916932, -0.15993518) * go_2(-1.0, -1.0);\n    result += mat4(0.28893283, -0.05677308, -0.2641288, -0.058937225, -0.16187571, 0.006647366, -0.063294955, 0.04766719, 0.60601914, -0.07831864, -0.15710756, -0.011491797, 0.15587467, -0.08105375, 0.07847514, -0.2803333) * go_2(-1.0, 0.0);\n    result += mat4(-0.077989794, -0.09871811, -0.3516344, 0.15292728, 0.010889273, 0.0011189661, -0.16118282, -0.018821161, -0.039708678, -0.00060983415, -0.06367813, 0.009148068, 0.03919827, 0.18782744, 0.028040757, -0.10230145) * go_2(-1.0, 1.0);\n    result += mat4(-0.4079609, 0.18640275, -0.12475227, 0.13891742, 0.25121725, 0.16942379, 0.14409852, 0.087600805, 0.045335658, -0.12683709, -0.0077387216, 0.06563413, -0.19857128, 0.106910795, -0.048285246, 0.10768945) * go_2(0.0, -1.0);\n    result += mat4(0.5989075, 0.20941062, -0.20086494, 0.13344856, 0.073034994, 0.22358665, 0.101664364, -0.13463663, 0.18816395, -0.061176624, -0.14712185, 0.027320342, -0.09529667, 0.031148786, -0.28744993, 0.18698911) * go_2(0.0, 0.0);\n    result += mat4(0.14799193, 0.39471942, -0.23340325, -0.4031061, 0.18926248, -0.11091216, 0.118981816, -0.09155061, 0.17049436, 0.19803695, -0.1513267, 0.023817873, 0.0090933135, -0.04134864, 0.060486555, 0.03536634) * go_2(0.0, 1.0);\n    result += mat4(-0.39094314, 0.01779997, 0.12710269, 0.0067333193, -0.31255835, -0.08206612, -0.048528638, 0.369439, -0.19351655, -0.03420455, 0.15831526, -0.052294146, -0.08481741, 0.0787108, 0.1312136, -0.108919285) * go_2(1.0, -1.0);\n    result += mat4(-0.16068119, -0.42190582, 0.19383872, -0.018445708, 0.09803051, -0.020769652, -0.022599563, -0.052448895, -0.20645833, -0.031432863, 0.0025441595, 0.03410379, -0.20268854, 0.04481527, 0.05191063, 0.42317194) * go_2(1.0, 0.0);\n    result += mat4(-0.12786235, -0.23936178, 0.116561726, 0.30756372, -0.09420156, -0.044529166, -0.03585749, 0.1829332, -0.23939075, 0.24030831, 0.019878127, -0.015069802, 0.24300557, -0.22558568, -0.104956664, -0.09393648) * go_2(1.0, 1.0);\n    result += mat4(-0.04607054, 0.012677649, -0.027597688, 0.1618836, 0.29210827, 0.014221155, -0.13591036, -0.06895336, -0.09559534, 0.07956421, -0.11112994, -0.13325493, 0.24562472, 0.11046177, 0.057847694, 0.0016315983) * go_3(-1.0, -1.0);\n    result += mat4(-0.03365951, 0.027391057, 0.09653403, -0.14718771, -0.049631152, -0.06467214, -0.058545876, 0.1424002, -0.06320376, 0.181183, 0.10249362, -0.16052136, 0.3013475, -0.04156266, 0.08862033, 0.06888033) * go_3(-1.0, 0.0);\n    result += mat4(0.10045977, -0.004198456, -0.025856055, 0.05739418, -0.1328637, -0.025975171, 0.06553717, 0.11301186, 0.0704087, -0.083569765, 0.16066101, -0.24453588, 0.25370175, 0.037184533, 0.062386766, -0.20025635) * go_3(-1.0, 1.0);\n    result += mat4(-0.017958941, 0.06417776, -0.1525265, 0.12451173, 0.14567685, -0.0049682115, -0.23973411, -0.0783304, -0.010629432, 0.08055161, 0.2028341, 0.17640644, -0.20445108, -0.055524793, -0.019326134, 0.081288636) * go_3(0.0, -1.0);\n    result += mat4(0.007882519, -0.03722546, 0.053249408, 0.00071846246, -0.07053029, -0.21583866, 0.1415364, -0.19486657, 0.20685542, 0.17660026, -0.32156837, 0.1746825, -0.14957622, -0.09224378, -0.098153435, -0.13054638) * go_3(0.0, 0.0);\n    result += mat4(0.10051427, -0.17398237, 0.09842799, -0.14187703, 0.116901085, -0.1229543, -0.0007776771, -0.20410055, -0.11373484, -0.111150615, -0.1974002, -0.11641459, 0.024105398, 0.24985977, 0.015871854, -0.10724633) * go_3(0.0, 1.0);\n    result += mat4(-0.18081793, 0.1209351, -0.12867971, -0.019415248, 0.062617876, -0.037130393, -0.07803658, -0.22862352, 0.2586428, -0.030090366, -0.11894069, 0.18087515, -0.40921417, 0.070013195, 0.030540073, 0.035120826) * go_3(1.0, -1.0);\n    result += mat4(-0.13185939, 0.12992652, 0.08125049, 0.075331174, 0.064219765, 0.056629725, -0.020012032, -0.0855444, -0.044063166, -0.05396545, -0.028002812, 0.21837157, -0.15206428, -0.12681007, 0.14895032, 0.12339962) * go_3(1.0, 0.0);\n    result += mat4(0.08066341, -0.14773634, -0.0212227, -0.014011867, -0.048505764, 0.075407125, -0.020620076, 0.0003291325, -0.21815202, -0.23136546, 0.10853532, -0.036058456, 0.10952532, -0.052677035, -0.13005799, 0.18398996) * go_3(1.0, 1.0);\n    result += vec4(0.022609137, -0.028548084, 0.024431901, 0.010504478);\n    return result;\n}\n//!DESC Anime4K-v4.0-Restore-CNN-(VL)-Conv-4x3x3x16\n//!HOOK MAIN\n//!BIND conv2d_1_tf\n//!BIND conv2d_1_tf1\n//!SAVE conv2d_2_tf\n//!WIDTH conv2d_1_tf.w\n//!HEIGHT conv2d_1_tf.h\n//!COMPONENTS 4\n#define go_0(x_off, y_off) (max((conv2d_1_tf_texOff(vec2(x_off, y_off))), 0.0))\n#define go_1(x_off, y_off) (max((conv2d_1_tf1_texOff(vec2(x_off, y_off))), 0.0))\n#define go_2(x_off, y_off) (max(-(conv2d_1_tf_texOff(vec2(x_off, y_off))), 0.0))\n#define go_3(x_off, y_off) (max(-(conv2d_1_tf1_texOff(vec2(x_off, y_off))), 0.0))\nvec4 hook() {\n    vec4 result = mat4(-0.069641694, 0.104958326, 0.14786446, 0.027633663, -0.004279524, -0.020451711, 0.0883571, -0.016224537, 0.13585235, 0.11078269, 0.20198658, -0.042161036, 0.020466218, 0.20994963, 0.20072585, -0.028024657) * go_0(-1.0, -1.0);\n    result += mat4(0.050872434, 0.12874635, 0.1298729, 0.115810685, 0.07087254, 0.09885682, 0.23018982, 0.19187538, 0.10953604, 0.0033836907, -0.13325337, 0.09830315, -0.06528767, 0.05096927, -0.016355392, -0.039334368) * go_0(-1.0, 0.0);\n    result += mat4(0.027010268, 0.018263958, 0.0360758, 0.016791478, 0.2815702, 0.15517488, 0.43415815, 0.044976447, -0.0070842914, -0.12546758, 0.16874593, 0.077622116, 0.02252915, 0.1769774, 0.07181055, -0.15128697) * go_0(-1.0, 1.0);\n    result += mat4(0.057129618, 0.118046716, 0.07237424, -0.07842637, -0.044214778, -0.12886304, 0.08603301, -0.10416606, -0.15852053, 0.3788151, 0.26181692, -0.09092249, 0.31635332, 0.064212754, 0.21923725, 0.07500004) * go_0(0.0, -1.0);\n    result += mat4(-0.16981383, 0.044409662, -0.3717617, -0.031610407, 0.03658662, -0.09459229, -0.09449437, -0.014000666, -0.19656453, 0.03934163, -0.16304104, -0.12761801, -0.06235523, 0.16438273, -0.036933117, -0.095564745) * go_0(0.0, 0.0);\n    result += mat4(0.09725091, 0.034022827, 0.17699842, 0.1079676, -0.13236652, 0.03718181, -0.06968635, -0.23288171, 0.10275666, 0.08464966, -0.37162134, -0.35782215, -0.11023659, 0.2519236, -0.035197742, -0.019324787) * go_0(0.0, 1.0);\n    result += mat4(-0.09968464, 0.01102193, 0.0073735216, 0.011999313, -0.004998707, 0.09518938, 0.045727003, -0.21544908, 0.006879454, -0.06398254, -0.12584935, -0.06759933, -0.0820037, -0.07775104, 0.021957919, -0.122708224) * go_0(1.0, -1.0);\n    result += mat4(-0.08869767, 0.031296413, -0.0034280645, 0.13778855, 0.10073061, -0.08393937, -0.032959275, -0.0500518, 0.010908757, -0.09189417, -0.057760105, 0.17652664, -0.08729078, -0.09639096, -0.25654703, 0.055152636) * go_0(1.0, 0.0);\n    result += mat4(0.0027847723, -0.12885433, 0.038065907, 0.17450769, 0.0864409, 0.04592345, -0.015443841, 0.077010944, 0.08967368, 0.06800111, -0.23636387, 0.35023567, 0.03165923, 0.03132063, 0.17964344, 0.035610788) * go_0(1.0, 1.0);\n    result += mat4(-0.032017227, -0.0022808525, -0.08470573, 0.05332408, -0.14674746, 0.025374275, -0.018281924, 0.041163016, 0.00096549373, 0.014724006, 0.004913065, 0.18494442, 0.034953076, -0.15731992, -0.13792977, 0.08041999) * go_1(-1.0, -1.0);\n    result += mat4(0.08305006, 8.6318905e-05, -0.007895379, 0.02731387, -0.061324496, 0.050034665, 0.22662131, -0.013876427, -0.074468784, -0.008136604, -0.23337875, -0.1742574, 0.011753501, -0.11666686, -0.22541048, -0.14549944) * go_1(-1.0, 0.0);\n    result += mat4(-0.028333234, 0.121047184, 0.06720256, -0.058930036, 0.030258363, 0.07292774, 0.06455556, 0.0019076486, 0.0073987027, 0.17144889, 0.06084024, -0.08762086, -0.114422195, -0.16595861, -0.08706028, -0.10736261) * go_1(-1.0, 1.0);\n    result += mat4(-0.02519315, -0.14611271, 0.0388848, 0.19481422, -0.05970354, -0.08391417, 0.18982239, -0.10447052, 0.15587378, -0.023997072, 0.0781739, 0.2182389, -0.023886079, -0.1422596, -0.13352804, 0.005008043) * go_1(0.0, -1.0);\n    result += mat4(0.08842712, -0.100292705, 0.18925671, 0.12198875, 0.061771665, -0.04473232, 0.025053164, 0.039047796, -0.1672479, -0.08934517, 0.33099812, -0.20269585, -0.21640155, -0.22029749, 0.16539703, -0.2442679) * go_1(0.0, 0.0);\n    result += mat4(-0.16332205, -0.101898365, 0.02919932, -0.11900455, 0.14442924, 0.0916815, 0.037550304, 0.024123482, 0.02042624, 0.033472955, -0.059437107, -0.18735693, -0.013749093, -0.06199881, -0.08685079, 0.04252364) * go_1(0.0, 1.0);\n    result += mat4(-0.09047013, -0.055188328, -0.09106191, -0.048969727, 0.05114009, -0.12753403, 0.07116141, 0.060749624, -0.074034564, -0.21952136, -0.09479503, 0.2753584, -0.014141759, -0.14883812, -0.0673838, -0.012279045) * go_1(1.0, -1.0);\n    result += mat4(0.013816464, -0.0747162, -0.19202435, -0.064403646, 0.34980014, 0.04375546, 0.20264609, 0.006684355, 0.11523799, 0.024674915, -0.08697566, -0.04662527, -0.12743855, -0.39463726, 0.0057380227, 0.01286557) * go_1(1.0, 0.0);\n    result += mat4(-0.08146522, 0.074080914, -0.16856177, -0.183158, 0.19228102, 0.12373886, 0.017574452, -0.01753772, 0.045071773, 0.07725093, 0.023422163, -0.011545186, 0.20751388, -0.10795588, 0.07606346, 0.10282933) * go_1(1.0, 1.0);\n    result += mat4(0.12512013, -0.102208994, -0.09125398, 0.12043188, -0.066011876, 0.08831903, -0.017038671, -0.005541508, -0.049607087, 0.08654939, -0.02037085, 0.26887566, 0.005012545, 0.01869507, -0.013064982, -0.010649147) * go_2(-1.0, -1.0);\n    result += mat4(0.006824864, -0.05071593, -0.20786697, -0.07327317, 0.011382597, 0.030494886, -0.04754353, -0.018284699, 0.01305972, -0.036589053, 0.26637617, 0.021887446, -0.026669119, -0.037982125, -0.063445956, -0.009104248) * go_2(-1.0, 0.0);\n    result += mat4(0.032602567, 0.07094331, 0.052653246, 0.08342047, -0.085082285, -0.14674088, -0.23073354, -0.07915851, 0.0017120204, 0.032407638, -0.039819505, 0.16942178, 0.023192152, -0.0353237, 0.10930186, 0.22939779) * go_2(-1.0, 1.0);\n    result += mat4(0.0010455973, -0.11821993, -0.12639599, 0.12250084, -0.12756817, 0.11478416, -0.1862587, 0.016819192, 0.02110181, -0.25492984, -0.1766048, 0.22188173, -0.21305011, 0.113442205, 0.04599144, -0.15840286) * go_2(0.0, -1.0);\n    result += mat4(-0.15086032, -0.17428935, 0.39080557, 0.07576757, 0.121703945, 0.17944208, -0.003140103, -0.11231332, 0.12102969, 0.15310267, 0.17578171, 0.40631834, -0.21299168, 0.024928993, 0.030104794, 0.020753227) * go_2(0.0, 0.0);\n    result += mat4(-0.098734386, -0.020072265, -0.14308836, -0.08490801, 0.017175158, 0.02250534, 0.04060829, 0.033022214, 0.0046218676, 0.17923212, 0.0112105915, 0.09574084, 0.14819936, -0.14692923, 0.12634254, 0.060762513) * go_2(0.0, 1.0);\n    result += mat4(0.030521613, -0.097913325, -0.016720278, 0.11273997, 0.013019863, -0.06557118, 0.0405774, 0.0915019, 0.022414956, -0.053254984, 0.18639986, 0.07820968, 0.06498986, 0.058922634, -0.02240318, -0.086019725) * go_2(1.0, -1.0);\n    result += mat4(0.2058775, 0.01502064, 0.05847032, 0.007249146, 0.086483665, 0.19420148, 0.03892261, -0.013546935, -0.07980237, 0.04347281, -0.10376214, -0.1366535, 0.05285337, 0.07213318, 0.3642818, -0.11331124) * go_2(1.0, 0.0);\n    result += mat4(-0.025740806, 0.14551482, -0.037410017, -0.17477523, -0.11853099, -0.060820814, -0.102599286, -0.13267937, -0.103053465, -0.014044828, -0.01888072, -0.06499249, 0.22311528, -0.051850274, -0.034120858, 0.044562567) * go_2(1.0, 1.0);\n    result += mat4(-0.21360217, 0.10093803, -0.0016407765, -0.1473997, 0.26524043, 0.02112132, 0.23173104, -0.013157391, 0.05945182, 0.044635538, 0.06031638, -0.21435826, -0.10147484, 0.069090195, 0.09641844, -0.09581093) * go_3(-1.0, -1.0);\n    result += mat4(-0.08576515, -0.122861005, 0.049567085, -0.085854456, 0.23809357, -0.024966082, -0.10294079, 0.046241313, 0.008621132, -0.08323767, 0.20277941, 0.163423, -0.07386535, -0.088738985, 0.05274358, -0.025479877) * go_3(-1.0, 0.0);\n    result += mat4(-0.041135542, -0.008365642, 0.17088248, 0.04025207, 0.13809255, -0.056895368, -0.01582834, 0.07361908, -0.00068995473, -0.09300962, 0.19117641, 0.24832036, -0.06572358, -0.026025, -0.019093119, -0.049720034) * go_3(-1.0, 1.0);\n    result += mat4(0.024900286, 0.11525501, 0.025882801, 0.037742402, 0.36976853, 0.052211333, -0.15143296, 0.1802276, -0.059080046, 0.017990451, 0.026395092, -0.12689115, -0.07705386, 0.1232379, 0.13273561, -0.12521964) * go_3(0.0, -1.0);\n    result += mat4(-0.19788785, 0.044887315, 0.07663442, 0.16688696, -0.2842248, -0.15684547, 0.028387763, 0.0063470444, -0.012245601, -0.038382255, -0.8187406, -0.25245667, 0.23014604, 0.22746666, 0.1594356, 0.16469443) * go_3(0.0, 0.0);\n    result += mat4(-0.12663333, 0.014730006, 0.03765697, 0.15704912, -0.106595434, -0.05317512, -0.081759915, -0.08797109, 0.064620756, -0.06341419, 0.16493447, 0.23102313, 0.068325415, -0.088058695, 0.16885915, 0.036382258) * go_3(0.0, 1.0);\n    result += mat4(0.035389822, -0.11811836, -0.035656307, -0.0680554, 0.1338908, 0.065852076, 0.023307983, 0.0675308, 0.09690683, 0.18170924, 0.09862692, -0.20964378, -0.08601271, -0.20016764, -0.01879598, -0.14629345) * go_3(1.0, -1.0);\n    result += mat4(-0.27183273, 0.013525998, -0.14995874, -0.23938845, -0.26218823, -0.0009874097, -0.13385512, -0.10664239, -0.048931994, 0.039898522, 0.047444753, 0.10934722, 0.10969629, 0.123539805, 0.11692802, 0.14172275) * go_3(1.0, 0.0);\n    result += mat4(-0.1656506, 0.019683002, 0.0221048, 0.12596753, 0.20420644, -0.07930122, 0.04653823, 0.11492255, -0.0050175437, -0.03271697, 0.013389486, 0.034583613, -0.2196601, -0.1615663, -0.013763388, -0.056037936) * go_3(1.0, 1.0);\n    result += vec4(-0.022956269, 0.029688787, -0.070148066, -0.07163476);\n    return result;\n}\n//!DESC Anime4K-v4.0-Restore-CNN-(VL)-Conv-4x3x3x16\n//!HOOK MAIN\n//!BIND conv2d_1_tf\n//!BIND conv2d_1_tf1\n//!SAVE conv2d_2_tf1\n//!WIDTH conv2d_1_tf.w\n//!HEIGHT conv2d_1_tf.h\n//!COMPONENTS 4\n#define go_0(x_off, y_off) (max((conv2d_1_tf_texOff(vec2(x_off, y_off))), 0.0))\n#define go_1(x_off, y_off) (max((conv2d_1_tf1_texOff(vec2(x_off, y_off))), 0.0))\n#define go_2(x_off, y_off) (max(-(conv2d_1_tf_texOff(vec2(x_off, y_off))), 0.0))\n#define go_3(x_off, y_off) (max(-(conv2d_1_tf1_texOff(vec2(x_off, y_off))), 0.0))\nvec4 hook() {\n    vec4 result = mat4(-0.15104648, 0.05522861, -0.0654341, -0.053517453, -0.08264124, -0.0062249107, -0.20364265, -0.05015117, -0.18837251, 0.030655831, 0.046844713, -0.20673253, -0.14042036, -0.05655449, 0.13994302, 0.011745607) * go_0(-1.0, -1.0);\n    result += mat4(-0.16517559, 0.1489214, -0.09149559, 0.025003506, -0.124926426, 0.16974348, -0.020857265, 0.08017403, 0.21836148, 0.0025619378, 0.2331612, 0.085599184, -0.030934382, -0.055194855, 0.09527726, -0.10081552) * go_0(-1.0, 0.0);\n    result += mat4(0.041800212, 0.028859638, 0.09395546, 0.05211183, -0.038541477, 0.021495212, 0.04862346, -0.007864793, 0.038407274, -0.13841268, -0.14963801, 0.26470762, 0.16691841, -0.07262008, 0.034374326, -0.14709206) * go_0(-1.0, 1.0);\n    result += mat4(0.00094978884, -0.028974704, -0.0900548, -0.08401967, -0.08935931, -0.043606587, -0.14497143, -0.05226239, -0.21516493, 0.19410603, -0.089924194, -0.04335071, -0.012618276, -0.2671613, 0.020422975, -0.037739716) * go_0(0.0, -1.0);\n    result += mat4(-0.13403237, -0.02524383, -0.03474901, 0.054432765, 0.11946775, 0.107336655, -0.1431715, -0.13370377, 0.015087512, -0.1917613, 0.073493585, 0.2788855, -0.010510839, 0.06891479, -0.06741307, -0.05271205) * go_0(0.0, 0.0);\n    result += mat4(-0.15432046, 0.04021662, -0.16979513, 0.13660534, -0.10518303, -0.10095502, -0.13092068, 0.022805348, -0.16676381, -0.4273298, 0.020867536, 0.3506733, -0.29459694, -0.055828743, -0.069241956, 0.04106382) * go_0(0.0, 1.0);\n    result += mat4(-0.08890133, 0.07549666, -0.040735144, -0.1506932, -0.22227979, -0.0762723, -0.17766447, -0.05741318, -0.21885683, 0.2379157, -0.15525854, -0.07306285, 0.15580738, -0.04394069, -0.19175608, 0.018283797) * go_0(1.0, -1.0);\n    result += mat4(-0.08503275, -0.105500385, -0.114987396, -0.07166016, -0.2147138, 0.09378708, 0.24550334, -0.0834075, -0.033147786, -0.022304727, -0.31062204, 0.027651973, 0.109098755, 0.18889032, 0.1163026, 0.13863255) * go_0(1.0, 0.0);\n    result += mat4(0.15266588, -0.14901319, 0.033916786, 0.09381096, -0.08196443, -0.16194504, 0.035789456, 0.21234898, -0.48724765, 0.2619442, -0.11215393, 0.25061038, 0.022344576, 0.0116525125, 0.111661114, -0.15242295) * go_0(1.0, 1.0);\n    result += mat4(0.020475458, 0.0797404, -0.13576819, 0.009681671, 0.030504882, 0.049232908, 0.022025917, 0.16912088, -0.23914136, -0.084663324, 0.020925451, -0.1023938, 0.035916872, -0.07538111, -0.11470242, 0.15238516) * go_1(-1.0, -1.0);\n    result += mat4(-0.12941381, 0.08509899, -0.029489802, -0.09148447, -0.089406274, -0.116145454, -0.08979843, 0.11908148, 0.15473351, -0.21687616, 0.12607013, -0.08244334, -0.079580925, -0.16613089, -0.09287793, -0.03412643) * go_1(-1.0, 0.0);\n    result += mat4(-0.023578499, 0.07394217, -0.13069086, -0.1060499, -0.07559958, -0.21839201, 0.1090753, 0.0787872, 0.07677037, -0.25998843, 0.20039314, 0.046882212, 0.31871012, -0.3048051, 0.15118991, -0.00518087) * go_1(-1.0, 1.0);\n    result += mat4(-0.15338503, -0.11057532, 0.075839415, -0.18592294, -0.0155324, 0.038140323, -0.10498194, 0.09070477, 0.05108992, -0.047939524, -0.091004305, 0.09649005, -0.10967152, -0.051909525, -0.05314551, 0.09661584) * go_1(0.0, -1.0);\n    result += mat4(-0.14458802, -0.053263694, -0.0010885567, 0.23342133, 0.01918937, 0.12026143, -0.15691495, 0.30480555, -0.08725869, 0.19082253, 0.3594973, 0.016653897, 0.045152336, -0.088590585, 0.0069655925, 0.1392425) * go_1(0.0, 0.0);\n    result += mat4(0.17944881, -0.17950764, 0.13282645, 0.030974053, 0.32233685, 0.18067117, -0.11472813, 0.097301506, -0.047649745, -0.1053861, -0.081039384, 0.035132434, 0.10204545, 0.085582554, -0.13153993, -0.021741152) * go_1(0.0, 1.0);\n    result += mat4(-0.15573682, 0.16409989, -0.22574787, -0.03877603, -0.18285516, 0.11638645, 0.18321282, -0.017770218, 0.18230622, 0.16433364, -0.12795393, -0.03805153, 0.14386104, -0.0891527, -0.056928284, -0.10961495) * go_1(1.0, -1.0);\n    result += mat4(0.257622, 0.052519716, -0.25421762, -0.1887382, -0.083568096, -0.0064690276, -0.029110614, 0.103327505, -0.17006217, 0.2254096, -0.29366904, 0.04302887, -0.10198446, -0.24423616, 0.16781262, -0.005019004) * go_1(1.0, 0.0);\n    result += mat4(0.103393994, -0.059044626, -0.18192382, 0.0990813, -0.26143607, 0.11036474, 0.04788275, -0.096738026, 0.12825653, 0.13631694, -0.077904984, -0.020790676, -0.25118098, 0.122588515, -0.049440473, -0.10758222) * go_1(1.0, 1.0);\n    result += mat4(0.06693113, -0.13647175, 0.131139, 0.13143918, 0.081720434, 0.117537096, 0.15387627, -0.008771362, 0.08513583, 0.023794742, -0.0661625, 0.115793936, 0.0023350024, 0.02215075, -0.0494433, -0.013404977) * go_2(-1.0, -1.0);\n    result += mat4(0.041419264, -0.17622781, 0.028418267, 0.12114493, -0.23587078, 0.08457395, 0.014364018, -0.103271864, -0.051572207, -0.026424447, 0.16755055, -0.10763651, -0.033440586, 0.068594255, -0.050668504, 0.1941505) * go_2(-1.0, 0.0);\n    result += mat4(-0.2780181, 0.037816502, -0.11516711, -0.09822884, 0.13762361, -0.14317706, 0.14350282, 0.000623895, -0.08601606, 0.08118504, 0.15497385, -0.04721711, -0.008936935, -0.014223618, -0.09641698, -0.013884213) * go_2(-1.0, 1.0);\n    result += mat4(0.14349665, -0.03144472, -0.057813704, 0.0667044, 0.09026094, 0.051366236, 0.11139983, -0.015782114, -0.18314016, -0.18774192, 0.0014838242, 0.15759028, 0.062388215, 0.13626057, 0.02576217, -0.06317815) * go_2(0.0, -1.0);\n    result += mat4(0.07151769, 0.14508991, 0.1736844, -0.11487795, -0.07999805, -0.07797908, 0.037923355, -0.059138823, -0.23531209, -0.040207293, -0.068355694, -0.024296658, -0.114820175, 0.19726487, 0.21772414, 0.03659222) * go_2(0.0, 0.0);\n    result += mat4(0.16858695, -0.12135113, 0.009391182, -0.081519485, 0.13340487, 0.07007004, 0.094124354, 0.035519842, -0.3320139, -0.06624027, -0.14716229, -0.09205287, 0.12664132, -0.05655441, 0.0123263765, 0.04641279) * go_2(0.0, 1.0);\n    result += mat4(0.19018422, -0.15428329, -0.009354114, 0.04165953, 0.11024837, -0.107493006, -0.05807292, -0.048029456, 0.24319384, -0.10542357, -0.013699952, 0.06228662, -0.06808749, -0.023227982, 0.16528323, -0.05610251) * go_2(1.0, -1.0);\n    result += mat4(-0.008616222, 0.077674195, -0.08638503, 0.09293109, 0.072474636, 0.05004233, -0.20591061, -0.005301386, -0.15486047, 0.15038474, 0.1262478, 0.021724822, 0.02274613, -0.3088281, -0.08437887, -0.10684698) * go_2(1.0, 0.0);\n    result += mat4(-0.16960032, 0.09365251, -0.030414175, -0.010766254, 0.18181023, 0.12130318, 0.08913089, -0.06070321, 0.05200306, 0.092584535, 0.17694671, 0.033796314, -0.038107123, -0.04335955, -0.049443472, 0.30465958) * go_2(1.0, 1.0);\n    result += mat4(0.07661484, -0.009945252, 0.12866217, -0.07592757, -0.21030053, 0.014371748, -0.072458774, -0.04700072, 0.15534303, 0.2007125, -0.15699059, -0.032897495, 0.08110436, -0.11243608, 0.008632577, -0.10153441) * go_3(-1.0, -1.0);\n    result += mat4(-0.034697928, 0.06928288, -0.2796273, 0.14405379, 0.12248569, 0.036539096, 0.06607706, 0.077684596, -0.16473202, 0.1665916, -0.29977503, 0.21047153, 0.13114224, -0.091579035, -0.045458574, 0.03254245) * go_3(-1.0, 0.0);\n    result += mat4(0.053284872, 0.053366095, -0.26152626, -0.03123967, -0.031794485, 0.17670582, -0.07450994, 0.017521491, -0.040290453, 0.38342363, -0.25021288, -0.014660264, 0.1621895, 0.25041878, -0.12124821, 0.068036206) * go_3(-1.0, 1.0);\n    result += mat4(0.11366693, -0.030863572, -0.07411263, 0.12475283, -0.046070684, -0.09033321, 0.013222701, 0.06798592, -0.32814804, 0.057653826, -0.14082801, -0.00217398, -0.22856179, -0.19058353, -0.20992154, -0.03701372) * go_3(0.0, -1.0);\n    result += mat4(0.20345633, -0.1332355, 0.27152926, -0.13477845, -0.25242096, -0.28281286, 0.31289554, 0.14284514, 0.53362453, -0.46766588, 0.4518293, -0.39291728, -0.3573227, -0.014670052, 0.0051881406, 0.16552156) * go_3(0.0, 0.0);\n    result += mat4(-0.15017267, -0.07792945, -0.204405, 0.13964304, -0.13642666, -0.10228306, 0.03238279, -0.08689329, -0.072262034, -0.0258388, 0.05689183, 0.055701543, -0.19800112, 0.012217054, -0.033292748, -0.047611095) * go_3(0.0, 1.0);\n    result += mat4(-0.014704416, -0.12203891, 0.066083655, -0.1409769, 0.0041513643, -0.087383606, -0.17498164, 0.11327789, -0.25947225, -0.0016027623, 0.08202566, 0.042270098, 0.006429511, -0.26576808, -0.08461341, 0.049376782) * go_3(1.0, -1.0);\n    result += mat4(0.0695189, -0.14753938, 0.09578246, -0.16607563, -0.0105561055, 0.17166016, 0.027422488, -0.14175262, -0.009492696, -0.23449713, 0.018270867, 0.14635146, 0.33451268, 0.030959005, -0.46468422, 0.024256868) * go_3(1.0, 0.0);\n    result += mat4(-0.16865666, -0.00015881563, -0.054488145, -0.06222717, -0.032101758, 0.06485387, -0.0028512608, 0.046645947, 0.017593225, -0.19447896, -0.024742266, 0.03970127, 0.29845607, -0.16168733, 0.035172883, 0.07924657) * go_3(1.0, 1.0);\n    result += vec4(0.103826486, 0.045373913, 0.11565896, -0.06568643);\n    return result;\n}\n//!DESC Anime4K-v4.0-Restore-CNN-(VL)-Conv-4x3x3x16\n//!HOOK MAIN\n//!BIND conv2d_2_tf\n//!BIND conv2d_2_tf1\n//!SAVE conv2d_3_tf\n//!WIDTH conv2d_2_tf.w\n//!HEIGHT conv2d_2_tf.h\n//!COMPONENTS 4\n#define go_0(x_off, y_off) (max((conv2d_2_tf_texOff(vec2(x_off, y_off))), 0.0))\n#define go_1(x_off, y_off) (max((conv2d_2_tf1_texOff(vec2(x_off, y_off))), 0.0))\n#define go_2(x_off, y_off) (max(-(conv2d_2_tf_texOff(vec2(x_off, y_off))), 0.0))\n#define go_3(x_off, y_off) (max(-(conv2d_2_tf1_texOff(vec2(x_off, y_off))), 0.0))\nvec4 hook() {\n    vec4 result = mat4(0.1851775, 0.053705044, 0.033816848, -0.018555025, -0.21204336, -0.01706974, 0.088259794, -0.13126148, 0.10729598, -0.043457437, 0.08634712, 0.09220895, 0.062131613, -0.01995871, 0.05181067, 0.18520063) * go_0(-1.0, -1.0);\n    result += mat4(0.1662002, -0.14197104, -0.052809287, 0.025287712, -0.08330898, -0.08998097, -0.15642618, -0.14941245, -0.03481203, 0.061857622, 0.26051775, -0.0005498248, 0.086427025, 0.024108192, -0.12418039, 0.022286376) * go_0(-1.0, 0.0);\n    result += mat4(0.058200672, -0.3073398, 0.17150162, -0.13394679, -0.075118184, -0.14607768, -0.006172172, 0.007731589, -0.21818224, -0.06449433, -0.038958784, 0.037722416, 0.28699976, -0.027563032, 0.23295315, 0.028444216) * go_0(-1.0, 1.0);\n    result += mat4(0.12871371, 0.0064904913, 0.14985761, -0.10923005, 0.17413563, 0.1599109, -0.08457703, 0.108153716, -0.08871187, -0.06661137, 0.2754416, -0.009667768, 0.39819396, 0.12392097, 0.14145902, 0.0019376524) * go_0(0.0, -1.0);\n    result += mat4(0.13893189, 0.12715353, 0.015191678, -0.21003054, -0.030412354, -0.01676613, -0.19799289, -0.006130075, 0.37676954, -0.14475077, -0.2065198, -0.30432892, -0.14944535, -0.09121536, -0.107600585, -0.24462196) * go_0(0.0, 0.0);\n    result += mat4(-0.11653076, -0.0068671284, -0.02249137, -0.17877012, -0.15063138, -0.13514869, 0.107643366, -0.03196477, -0.086422764, 0.3079287, 0.17584166, -0.032449376, -0.06917114, -0.2682637, -0.18978168, -0.037039287) * go_0(0.0, 1.0);\n    result += mat4(0.12014731, -0.030360512, -0.12954475, -0.110275604, -0.077214256, 0.019689744, 0.22149551, -0.002266716, 0.09697784, -0.124532826, -0.16776511, -0.034212478, -0.36935154, 0.016926935, 0.1363609, 0.20415346) * go_0(1.0, -1.0);\n    result += mat4(-0.11199535, -0.001692563, -0.09058429, -0.08437503, 0.092625685, 0.06046257, 0.25509837, -0.011657033, -0.17949764, -0.10718947, -0.1180669, -0.24681842, -0.1747311, 0.0014518246, -0.042863015, 0.06103357) * go_0(1.0, 0.0);\n    result += mat4(0.14979295, -0.037154514, 0.01957725, 0.012282435, 0.09168596, -0.05552286, 0.111671515, 0.0078630615, -0.10319766, -0.06416261, -0.23097566, -0.13931875, 0.2110811, 0.013095802, -0.2306504, -0.025639111) * go_0(1.0, 1.0);\n    result += mat4(-0.10091975, -0.10095426, -0.023449723, -0.022170888, 0.054953706, -0.13049407, 0.08289061, 0.023241632, 0.08735388, -0.0058387457, 0.17897247, 0.011434436, 0.008181139, -0.0034718404, -0.015372735, -0.07657766) * go_1(-1.0, -1.0);\n    result += mat4(-0.023442164, 0.07535702, 0.024391165, -0.050532013, 0.044168636, 0.0062343236, -0.019756999, -0.009695123, 0.10102337, 0.0052776975, -0.14944167, -0.060957722, 0.24367364, -0.08069369, 0.12170072, -0.047048368) * go_1(-1.0, 0.0);\n    result += mat4(-0.18376935, -0.08407229, -0.12943378, 0.0738419, -0.12404976, -0.13367929, 0.11265896, -0.021353, 0.003783386, 0.50088304, 0.14058582, 0.041053623, 0.038247623, -0.014179976, 0.007905778, -0.042492237) * go_1(-1.0, 1.0);\n    result += mat4(-0.046272535, 0.052449115, 0.17190954, -0.004745371, -0.045572635, -0.09292636, 0.36309823, 0.16673928, -0.099154025, -0.109614775, 0.17803112, 0.19907133, -0.14306267, 0.06898593, 0.11493454, 0.06795014) * go_1(0.0, -1.0);\n    result += mat4(0.26181114, -0.044014625, -0.21605036, -0.08646438, 0.21038742, -0.084986, 0.0504626, 0.17514943, -0.25218952, -0.18691514, 0.057650108, 0.08653614, -0.101205684, 0.03176334, 0.18569492, 0.17973189) * go_1(0.0, 0.0);\n    result += mat4(-0.0339215, 0.20112811, -0.12986277, 0.028961731, -0.056813832, 0.04451147, -0.07827432, -0.0860976, 0.096853435, 0.3483546, -0.35758162, -0.11749375, -0.035918653, 0.06140711, -0.08520154, 0.02418808) * go_1(0.0, 1.0);\n    result += mat4(-0.09643022, -0.10491069, 0.0068604187, 0.023679713, 0.096521445, -0.29323488, 0.33353668, 0.112864286, -0.1172182, -0.07233183, 0.06607239, 0.08589609, 0.055790007, 0.14396138, -0.14191268, 0.00034840964) * go_1(1.0, -1.0);\n    result += mat4(0.15357164, -0.038462736, 0.08143956, 0.1744909, 0.40503287, -0.114508316, 0.003937322, 0.2536635, -0.042445306, -0.15622465, 0.09155284, 0.010992155, -0.20646071, 0.022801135, 0.08894491, 0.069300614) * go_1(1.0, 0.0);\n    result += mat4(-0.12663515, 0.023849454, -0.053604446, 0.12082873, -0.247968, -0.020969635, -0.03831894, -0.014617553, 0.22630337, 0.037801865, 0.052950703, 0.04285706, -0.14487264, 0.20786528, -0.08719664, 0.1752347) * go_1(1.0, 1.0);\n    result += mat4(-0.073527604, -0.050752833, 0.051830504, 0.32868716, 0.17474994, 0.016937364, -0.08792601, -0.024481766, -0.022229593, 0.030706186, 0.09213566, -0.076506205, 0.073404044, 0.10368055, -0.175889, -0.08453031) * go_2(-1.0, -1.0);\n    result += mat4(-0.06838216, 0.007698341, 0.063972116, -0.015604406, 0.16135305, 0.18044342, 0.024137018, -0.23326185, 0.13235588, -0.009096587, -0.058368143, -0.077040404, 0.0011419816, -0.09246194, 0.061036937, 0.049564146) * go_2(-1.0, 0.0);\n    result += mat4(0.023225296, -0.00060856267, -0.07775185, 0.016958566, -0.2641349, -0.08263046, -0.15350416, -0.30203494, 0.113956556, -0.010813236, -0.017738314, -0.13689043, -0.10318342, 0.025793184, -0.010336172, 0.09733422) * go_2(-1.0, 1.0);\n    result += mat4(-0.04462596, 0.052866418, -0.34754288, 0.05540498, -0.24492586, -0.32016864, 0.18145293, 0.24873725, 0.32388234, -0.034801524, -0.1347588, -0.07565546, 0.015183539, 0.05059595, 0.08090056, 0.05930932) * go_2(0.0, -1.0);\n    result += mat4(0.045346696, -0.052527856, 0.052270077, 0.13417454, 0.05200045, 0.028119288, 0.005115497, 0.22952151, -0.2158375, 0.12241308, 0.3507457, 0.08616576, 0.07592416, 0.28470486, 0.3432788, 0.24857087) * go_2(0.0, 0.0);\n    result += mat4(0.21311626, 0.052607164, 0.1248861, 0.20193806, 0.045226507, 0.14512901, -0.15103437, -0.17926466, 0.11657411, -0.32711068, -0.16332194, -0.07793982, -0.21802668, 0.5183869, -0.13567342, 0.07823041) * go_2(0.0, 1.0);\n    result += mat4(0.00796368, 0.048073012, -0.14537893, -0.021708772, 0.036246423, 0.1062395, 0.12605369, 0.007073524, -0.1572743, 0.07439501, 0.089162275, -0.0039608316, 0.332032, -0.05461242, -0.17615359, -0.10240517) * go_2(1.0, -1.0);\n    result += mat4(0.20636982, -0.0024615112, -0.10625786, 0.024270926, 0.061810836, -0.13585201, -0.16581286, 0.23549418, 0.01928842, 0.07404979, -0.054449487, 0.04096373, 0.046939734, 0.003980803, 0.02111498, 0.064925276) * go_2(1.0, 0.0);\n    result += mat4(0.10485388, 0.06850885, -0.11292169, 0.16991565, -0.15282536, 0.124175504, -0.050431166, -0.06689582, -0.00059811946, 0.033696912, 0.11055047, 0.033060126, -0.17472714, 0.0048819613, -0.04478706, -0.1344572) * go_2(1.0, 1.0);\n    result += mat4(-0.20473132, 0.056477875, 0.059559986, 0.115130566, -0.058425788, -0.035971727, 0.08334707, -0.096510135, -0.23206294, 0.10635798, -0.21575621, -0.07063254, 0.03877511, -0.107549034, 0.22248401, 0.21702304) * go_3(-1.0, -1.0);\n    result += mat4(-0.02557767, 0.09886609, -0.100499466, 0.16687396, -0.084830604, 0.03150401, -0.049512494, 0.05595696, -0.13193256, -0.08585273, 0.14247662, 0.12290477, -0.07168309, 0.14531752, -0.048359327, 0.27716598) * go_3(-1.0, 0.0);\n    result += mat4(0.13297586, 0.20674329, 0.14469388, 0.08981846, -0.004231366, -0.02819193, 0.15470329, 0.17299837, 0.113062344, -0.22716297, -0.21754944, -0.00083956274, -0.14160508, 0.1808253, 0.11268379, 0.27335623) * go_3(-1.0, 1.0);\n    result += mat4(0.07497518, -0.06799594, -0.018158078, -0.00038999433, -0.15169668, -0.06928238, -0.33672288, -0.105485775, 0.33106267, 0.06698315, 0.019718744, -0.06810211, -0.35186404, -0.29145968, -0.056863394, 0.21498048) * go_3(0.0, -1.0);\n    result += mat4(-0.013215512, -0.24763754, 0.20965266, 0.1068435, -0.13234195, 0.053566497, 0.05061848, -0.28645232, 0.15518288, 0.23247199, 0.017553907, -0.25181335, -0.048030723, -0.06663929, -0.111026704, -0.12663394) * go_3(0.0, 0.0);\n    result += mat4(-0.010501938, -0.17995767, 0.06010859, 0.050185587, 0.108627126, -0.101203434, 0.07558728, 0.060466755, -0.106942676, -0.35854608, 0.16015992, 0.16823332, -0.06543775, -0.37310675, 0.014043972, -0.18328045) * go_3(0.0, 1.0);\n    result += mat4(0.09712849, 0.013983463, 0.07291423, 0.031715546, 0.030862397, 0.045510456, -0.22066842, 0.063464865, 0.11721659, -0.10596602, -0.20611264, 0.052158818, -0.3961766, -0.03781582, 0.17633812, 0.1316111) * go_3(1.0, -1.0);\n    result += mat4(-0.25029674, 0.07153423, -0.35125682, -0.18255402, -0.19569087, 0.00432772, -0.0969035, -0.24648514, -0.0040922165, 0.037500706, -0.038137026, 0.056214277, -0.048258524, 0.03567822, -0.05033007, -0.24696785) * go_3(1.0, 0.0);\n    result += mat4(-0.03465209, -0.012495964, 0.22782089, 0.012034795, 0.2916752, 0.08264436, 0.15387125, -0.1473455, -0.15614432, 0.05536727, -0.027079755, 0.010725311, -0.03325222, -0.089212805, -0.10559839, -0.19647683) * go_3(1.0, 1.0);\n    result += vec4(0.0001705175, -0.031081453, 0.010100773, -0.027214011);\n    return result;\n}\n//!DESC Anime4K-v4.0-Restore-CNN-(VL)-Conv-4x3x3x16\n//!HOOK MAIN\n//!BIND conv2d_2_tf\n//!BIND conv2d_2_tf1\n//!SAVE conv2d_3_tf1\n//!WIDTH conv2d_2_tf.w\n//!HEIGHT conv2d_2_tf.h\n//!COMPONENTS 4\n#define go_0(x_off, y_off) (max((conv2d_2_tf_texOff(vec2(x_off, y_off))), 0.0))\n#define go_1(x_off, y_off) (max((conv2d_2_tf1_texOff(vec2(x_off, y_off))), 0.0))\n#define go_2(x_off, y_off) (max(-(conv2d_2_tf_texOff(vec2(x_off, y_off))), 0.0))\n#define go_3(x_off, y_off) (max(-(conv2d_2_tf1_texOff(vec2(x_off, y_off))), 0.0))\nvec4 hook() {\n    vec4 result = mat4(-0.026301445, -0.021575214, 0.22165509, 0.059994068, 0.03341161, 0.1831188, 0.20342293, 0.110160105, 0.03908121, 0.020673111, 0.07239561, 0.038754333, 0.15266368, 0.16526422, 0.062376205, -0.09759537) * go_0(-1.0, -1.0);\n    result += mat4(0.19817191, 0.10267733, 0.17744653, 0.23283184, 0.18810122, 0.2708428, -0.12651879, 0.020756349, 0.039632563, -0.22201295, 0.04873703, 0.09159713, 0.13838065, 0.21169297, 0.30816007, 0.044463675) * go_0(-1.0, 0.0);\n    result += mat4(-0.27859214, 0.07277634, 0.0021458792, 0.0089682285, -0.069680706, 0.090415835, -0.057762265, 0.18703683, -0.03514389, -0.102816254, -0.036509827, 0.038066104, -0.0168311, 0.094478935, 0.04079697, -0.049064912) * go_0(-1.0, 1.0);\n    result += mat4(-0.20913245, -0.110538535, -0.08584027, -0.1222067, 0.05414807, -0.045247085, 0.07351766, -0.002078549, -0.1270987, -0.10164512, -0.1857815, 0.08845066, -0.03743333, -0.098948084, 0.21244387, 0.10441866) * go_0(0.0, -1.0);\n    result += mat4(0.015990427, 0.36396438, -0.24094687, 0.30236533, -0.13271736, 0.06057376, -0.19678196, -0.28577125, -0.25427434, -0.08400598, 0.07284403, -0.18552442, -0.16425897, 0.097259276, -0.32386774, -0.2190484) * go_0(0.0, 0.0);\n    result += mat4(-0.004581924, -0.13954072, -0.122360416, 0.14132866, -0.08529257, -0.013296556, 0.0848472, 0.09336581, 0.10332182, -0.016313016, 0.07103558, 0.032564916, -0.13478759, -0.20207484, 0.12986964, 0.1219679) * go_0(0.0, 1.0);\n    result += mat4(0.09817874, -0.10573357, 0.100535244, 0.19608764, -0.13303067, 0.024192972, -0.030689823, 0.02574889, 0.051233094, 0.03489235, -0.18465245, -0.06943822, -0.031604882, 0.1519888, 0.09348508, 0.09187296) * go_0(1.0, -1.0);\n    result += mat4(-0.21365458, -0.23696984, 0.13097638, -0.09435498, 0.16467983, -0.066370346, 0.1269104, -0.095128186, 0.09954892, 0.12489504, -0.43418056, 0.106512725, -0.17860703, -0.07114084, -0.07630834, -0.26642478) * go_0(1.0, 0.0);\n    result += mat4(-0.009044342, 0.02711196, -0.14873673, 0.015405045, 0.0071443473, -0.025285944, 0.07409282, 0.06338527, 0.0149676185, 0.011741382, -0.2133069, -0.028912885, 0.19420496, 0.039629057, 0.057636812, 0.15214856) * go_0(1.0, 1.0);\n    result += mat4(0.07629928, 0.25540486, -0.050925937, -0.18136702, 0.02261603, 0.22343902, 0.003270321, 0.10735731, -0.12541203, -0.10208828, 0.012832783, 0.2591262, 0.08122926, -0.009837677, 0.10308358, 0.19236866) * go_1(-1.0, -1.0);\n    result += mat4(0.0896358, 0.27571487, 0.04406029, -0.047453407, -0.08587119, 0.16366854, 0.20622262, 0.08347545, -0.3501584, -0.28434548, -0.07592983, 0.09098784, 0.07605388, 0.09677056, 0.0015295541, 0.05102585) * go_1(-1.0, 0.0);\n    result += mat4(0.18255898, 0.18618028, 0.0017002645, -0.013004655, -0.06436534, 0.13967068, 0.063077755, -0.10632303, -0.20803222, -0.028537111, -0.03144366, -0.08555215, 0.05154303, 0.02431626, 0.15246728, -0.013708507) * go_1(-1.0, 1.0);\n    result += mat4(-0.020998938, -0.05026291, 0.03700117, 0.00830308, -0.1949294, 0.0026698054, -0.034649856, 0.19784226, -0.083901435, -0.069783084, -0.1504053, 0.16595264, -0.07480141, 0.16067508, 0.06010996, -0.021359695) * go_1(0.0, -1.0);\n    result += mat4(-0.040828142, -0.20158486, 0.034770954, -0.1894161, 0.11665004, 0.29729164, -0.10584386, 0.13165873, -0.18863006, -0.26719162, -0.047613148, -0.12728356, -0.2033613, 0.10550052, 0.20095508, -0.11275811) * go_1(0.0, 0.0);\n    result += mat4(-0.0785033, -0.1896073, -0.051492307, -0.1694358, 0.1368308, 0.049355216, -0.05707422, 0.079159185, 0.024578957, -0.0923136, 0.089215435, 0.28670043, 0.027932687, 0.06510816, 0.10810999, 0.05990052) * go_1(0.0, 1.0);\n    result += mat4(0.08135192, 0.0001326522, -0.16098668, -0.18663193, -0.10280192, 0.078255914, 0.047648013, 0.08326376, 0.055962667, 0.06302574, -0.080121025, -0.031820554, -0.019117938, 0.12515336, 0.09794088, -0.03276838) * go_1(1.0, -1.0);\n    result += mat4(0.280923, 0.24079335, 0.007883573, 0.06270414, 0.3055441, 0.19291803, -0.16041607, 0.14836526, 0.0013885222, 0.04538063, 0.10742898, -0.064491205, 0.048174977, 4.237692e-05, -0.15194727, 0.024381457) * go_1(1.0, 0.0);\n    result += mat4(-0.0009164131, -0.031949926, 0.0076425644, -0.036870714, -0.0031292974, 0.017726978, -0.20172147, -0.0770472, 0.26379177, 0.108997814, 0.08069395, 0.2126177, 0.012075376, -0.029457828, 0.062730506, -0.15754452) * go_1(1.0, 1.0);\n    result += mat4(0.09167904, -0.2657421, -0.03443356, 0.03315832, -0.015365421, -0.1029612, -0.108251, 0.04261033, -0.097120754, -0.05616668, -0.09275983, 0.024902184, 0.050058514, -0.013761632, 0.07555132, -0.0046676896) * go_2(-1.0, -1.0);\n    result += mat4(-0.10743835, -0.0007361781, -0.042085417, -0.08237517, -0.10094376, -0.24007876, 0.13924706, -0.07526801, 0.01158322, 0.15491122, 0.0069442675, -0.004242352, 0.11429785, 0.02994726, -0.11829945, -0.04108612) * go_2(-1.0, 0.0);\n    result += mat4(0.073622055, -0.064717196, -0.0025231615, 0.13256475, 0.20159899, 0.047977835, -0.10289233, -0.18419135, -0.00888952, 0.059428576, -0.053062655, -0.02730631, 0.14545685, -0.08686949, 0.17454128, 0.035443828) * go_2(-1.0, 1.0);\n    result += mat4(-0.010146019, 0.06712568, 0.12614638, 0.023590917, 0.025756737, 0.06603747, -0.17108095, -0.06179699, 0.027241204, -0.13196802, 0.043475866, -0.0397495, 0.05306092, 0.035672903, 0.047219284, -0.16680142) * go_2(0.0, -1.0);\n    result += mat4(0.079427816, -0.06716479, 0.19028603, -0.19694683, -0.061598092, -0.07471188, 0.21170339, 0.30140215, -0.0023369973, 0.04688297, -0.14154115, 0.19283508, 0.1339858, -0.09116279, 0.15305163, 0.029108394) * go_2(0.0, 0.0);\n    result += mat4(-0.14902157, -0.03339153, -0.08532003, -0.10736339, 0.08702709, 0.07607574, -0.09955836, -0.016585784, -0.030078214, -0.060374748, -0.2854279, 0.02441719, 0.034877967, 0.2099041, 0.11125731, -0.059071556) * go_2(0.0, 1.0);\n    result += mat4(-0.08436325, 0.06893047, -0.045362443, -0.02237741, -0.07583875, -0.034830183, -0.024008518, -0.2882329, -0.011109783, 0.101859994, 0.091137715, 0.0020565533, -0.044729806, -0.18168025, 0.069466636, 0.04994174) * go_2(1.0, -1.0);\n    result += mat4(0.11915174, 0.089596465, -0.18965814, 0.015218237, 0.13500094, 0.19921367, -0.008298205, 0.29650384, -0.049439427, -0.27590424, 0.36169067, -0.030582754, 0.02151196, 0.019915426, 0.04543398, 0.16126189) * go_2(1.0, 0.0);\n    result += mat4(0.1620274, -0.08264547, 0.082442135, -0.0034478644, 0.09888509, -0.0034957859, -0.107241705, -0.17729597, -0.05138647, 0.02052103, -0.019507123, 0.037574988, -0.1694345, 0.17871588, -0.22510391, 0.019049853) * go_2(1.0, 1.0);\n    result += mat4(-0.10962245, -0.1329873, -0.060855392, 0.025941676, -0.19536193, -0.120365486, -0.04313703, -0.052912965, 0.20854498, 0.08341353, 0.008687068, -0.20432276, 0.15677948, -0.19000018, 0.01821201, -0.041512605) * go_3(-1.0, -1.0);\n    result += mat4(0.012287526, -0.14180368, -0.098788455, 0.025949089, 0.09442778, 0.2247651, -0.12453263, 0.10435483, 0.274603, 0.06133054, 0.10506106, 0.14727746, -0.048299775, -0.082819685, 0.07319359, -0.047460355) * go_3(-1.0, 0.0);\n    result += mat4(-0.070726536, -0.034744017, 0.07521428, 0.070649154, -0.05958955, -0.100232825, -0.010651838, 0.045392875, 0.2930271, -0.04952355, 0.3112155, 0.117203265, 0.025166962, 0.11176862, 0.06716659, 0.07175864) * go_3(-1.0, 1.0);\n    result += mat4(-0.011560962, -0.14032063, -0.17424704, 0.07652749, -0.04220116, 0.052874275, -0.00225693, -0.031843517, -0.07520102, -0.13775803, 0.2449317, 0.069658786, 0.052280303, -0.105218224, 0.03574522, -0.020500354) * go_3(0.0, -1.0);\n    result += mat4(0.08793712, 0.26712346, 0.08315631, 0.23813692, -0.04439029, 0.031587064, 0.09561177, -0.13380238, -0.24982157, 0.31701845, -0.3875432, 0.10487225, 0.09201869, -0.037252493, -0.006935219, -0.14650282) * go_3(0.0, 0.0);\n    result += mat4(0.077635325, 0.13732299, -0.071563005, 0.096517466, -0.15051986, -0.111744404, 0.03996857, -0.052670125, -0.1819665, 0.054554947, -0.13774712, -0.20061246, -0.0023742192, 0.15647805, -0.024121126, 0.075497724) * go_3(0.0, 1.0);\n    result += mat4(0.0073632775, -0.06535298, 0.039895996, 0.20666869, 0.13625242, 0.04823007, -0.07135618, 0.04787906, 0.01383074, 0.15382123, -0.15519714, 0.056721795, 0.061946746, -0.0586851, 0.028934354, -0.02264129) * go_3(1.0, -1.0);\n    result += mat4(-0.19791882, -0.111910924, -0.010451344, -0.30566537, -0.1416239, -0.14523096, 0.116883226, -0.18241516, 0.2680614, -0.18487626, 0.17472346, 0.08346682, -0.14510359, -0.029229192, -0.005879142, 0.050247498) * go_3(1.0, 0.0);\n    result += mat4(0.030153519, -0.092469186, -0.022912916, 0.10200855, -0.04237032, -0.05917764, 0.10479645, -0.05619482, -0.18949397, -0.019547248, 0.013868889, -0.1524476, 0.14048979, -0.032521486, 0.1322921, 0.070972025) * go_3(1.0, 1.0);\n    result += vec4(0.012053958, -4.6962363e-05, 0.0020099226, -0.033494607);\n    return result;\n}\n//!DESC Anime4K-v4.0-Restore-CNN-(VL)-Conv-4x3x3x16\n//!HOOK MAIN\n//!BIND conv2d_3_tf\n//!BIND conv2d_3_tf1\n//!SAVE conv2d_4_tf\n//!WIDTH conv2d_3_tf.w\n//!HEIGHT conv2d_3_tf.h\n//!COMPONENTS 4\n#define go_0(x_off, y_off) (max((conv2d_3_tf_texOff(vec2(x_off, y_off))), 0.0))\n#define go_1(x_off, y_off) (max((conv2d_3_tf1_texOff(vec2(x_off, y_off))), 0.0))\n#define go_2(x_off, y_off) (max(-(conv2d_3_tf_texOff(vec2(x_off, y_off))), 0.0))\n#define go_3(x_off, y_off) (max(-(conv2d_3_tf1_texOff(vec2(x_off, y_off))), 0.0))\nvec4 hook() {\n    vec4 result = mat4(-0.06738501, 0.034009207, -0.21538448, 0.14296548, 0.12896985, -0.23526315, -0.08848608, 0.019602662, 0.14937137, 0.11353096, 0.11884168, -0.016765572, 0.030985225, 0.046430565, 0.06614828, -0.19202724) * go_0(-1.0, -1.0);\n    result += mat4(-0.10326068, 0.11014975, 0.17069744, -0.21474148, 0.16761585, 0.13434832, -0.101021074, 0.006307025, 0.07478008, -0.1060066, 0.035315692, 0.033488914, -0.24906659, 0.06269967, 0.11120735, -0.040928528) * go_0(-1.0, 0.0);\n    result += mat4(0.09334615, 0.057705753, 0.12213245, -0.06402275, 0.30694544, 0.034585163, 0.20345578, 0.07489286, 0.07483618, -0.14240396, 0.034846418, -0.03811241, 0.010882573, 0.13204294, 0.017563924, -0.047203008) * go_0(-1.0, 1.0);\n    result += mat4(-0.21673942, -0.024010994, -0.10238504, -0.041160326, 0.06838163, -0.20950818, 0.06526309, -0.079094924, 0.02208821, -0.28130978, 0.086275116, -0.089067616, 0.12133826, -0.062600106, -0.020521903, -0.07654401) * go_0(0.0, -1.0);\n    result += mat4(-0.03055029, -0.15683146, -0.20331301, -0.06252028, 0.13350682, 0.20338707, 0.038425338, 0.1581342, -0.27322498, -0.14999662, -0.16681097, 0.0971585, -0.20014858, -0.081635274, -0.0781877, -0.20625232) * go_0(0.0, 0.0);\n    result += mat4(0.38375977, -0.019825654, 0.1886721, 0.22616312, 0.3402173, 0.1825304, -0.05531195, 0.30973226, -0.2676023, 0.14413352, 0.021706983, 0.01732799, 0.23466855, -0.13805965, 0.22570935, 0.018103868) * go_0(0.0, 1.0);\n    result += mat4(-0.15169825, 0.0270689, -0.2503316, 0.17289825, -0.16437647, 0.039233048, -0.35572487, -0.048393793, 0.19270042, 0.24260359, 0.12041881, -0.0009793913, 0.11656858, 0.11007414, -0.0757491, 0.047933612) * go_0(1.0, -1.0);\n    result += mat4(-0.18657999, -0.11252566, -0.05237504, -0.07368097, 0.13882741, -0.13710637, -0.006996468, -0.062354874, 0.23452504, 0.15333645, -0.0022776406, -0.17910439, 0.03629509, -0.16264829, -0.010011833, -0.15313338) * go_0(1.0, 0.0);\n    result += mat4(-0.060544558, -0.04913478, -0.061717357, 0.02323648, 0.28739056, -0.07434013, 0.19110644, 0.100050166, 0.0073363045, 0.08185653, -0.024797903, -0.14424153, -0.20838726, 0.16154376, -0.048517212, -0.025453888) * go_0(1.0, 1.0);\n    result += mat4(0.14975396, -0.13142908, 0.36210674, -0.054021083, -0.10632155, 0.045697935, -0.18946633, 0.02228141, -0.08919603, 0.09800842, -0.17634438, 0.09512711, -0.03425503, -0.12298555, -0.05354435, -0.17112055) * go_1(-1.0, -1.0);\n    result += mat4(0.09958265, -0.057276618, -0.16262266, -0.06415915, 0.14579074, -0.36784375, 0.08034197, -0.04537706, 0.005460582, 0.22313322, 0.07382161, 0.014990379, 0.044636846, -0.2811128, -0.22621547, -0.06044004) * go_1(-1.0, 0.0);\n    result += mat4(0.10569276, -0.03738662, 0.16100396, 0.058593616, -0.048862137, -0.08796426, 0.20101094, -0.11039573, 0.17196764, -0.04601554, 0.008571281, -0.073729075, 0.051433694, -0.051276565, 0.087334655, -0.0360379) * go_1(-1.0, 1.0);\n    result += mat4(0.011119538, -0.28781965, 0.28637868, -0.1742508, -0.07121849, 0.10379717, 0.012615981, -0.029563965, -0.18678424, 0.05291095, 0.039143506, -0.028248642, -0.014103922, 0.029155696, 0.10433492, 0.16305852) * go_1(0.0, -1.0);\n    result += mat4(-0.2231037, -0.13697462, -0.29124337, 0.08519773, 0.15893684, -0.17763218, 0.06950923, 0.34361118, -0.024844287, 0.044008408, -0.033844844, -0.086971916, -0.07884748, 0.2543499, 0.056884114, 0.10068364) * go_1(0.0, 0.0);\n    result += mat4(-0.07710048, -0.23218372, 0.04346047, 0.21769643, 0.06473219, -0.18066105, -0.2511205, 0.15309611, 0.04535977, 0.16450433, 0.10846344, 0.0016952346, -0.010874939, 0.28966382, -0.121990964, 0.12956186) * go_1(0.0, 1.0);\n    result += mat4(-0.007910202, 0.17766511, 0.14364475, 0.1016258, 0.0051045395, 0.18691733, 0.005813767, -0.0070582186, 0.019418601, -0.1604435, 0.016088275, -0.18265302, -0.15719391, -0.17369832, -0.036745597, -0.19647408) * go_1(1.0, -1.0);\n    result += mat4(0.08938396, -0.0073808245, 0.11225727, -0.012303106, 0.096785046, 0.030483445, 0.027719889, -0.052584838, -0.14887555, -0.03422243, 0.12646855, -0.1722482, 0.010239037, 0.06406088, -0.20053658, 0.01964698) * go_1(1.0, 0.0);\n    result += mat4(-0.120734036, -0.12450362, -0.06582111, 0.1639675, -0.19787048, -0.08049789, -0.014257596, 0.058436662, -0.0009387449, -0.08698089, -0.017400503, 0.06295286, 0.09890349, -0.057190523, -0.103520766, -0.04207548) * go_1(1.0, 1.0);\n    result += mat4(-0.0118413875, -0.031288836, 0.09749554, -0.012266401, -0.07998591, 0.22615653, -0.06207416, 0.03257896, -0.076378696, -0.079426095, -0.13968349, -0.15423697, -0.1091681, -0.02893125, -0.032659534, -0.063735925) * go_2(-1.0, -1.0);\n    result += mat4(0.119372696, 0.013176554, -0.029381052, 0.21919228, 0.045041792, 0.24844484, 0.26363325, 0.08480674, 0.087083444, 0.11984778, -0.088715754, 0.06421046, 0.05225977, -0.05140334, -0.055052705, -0.049854077) * go_2(-1.0, 0.0);\n    result += mat4(0.0035781674, 0.0861361, -0.07675145, -0.056479637, 0.16973391, -0.12113791, 0.10729832, -0.03773517, 0.058618728, 0.12148276, 0.17260705, -0.06968724, 0.076358154, -0.15307103, 0.17700425, -0.13467014) * go_2(-1.0, 1.0);\n    result += mat4(-0.02752418, -0.06366472, -0.025610954, 0.0013539721, -0.06465272, 0.0806373, -0.07336035, 0.10114861, 0.0041146413, 0.15878421, -0.044668555, -0.12150811, -0.1071482, -0.05086587, 0.18589285, 0.05065092) * go_2(0.0, -1.0);\n    result += mat4(0.07200056, 0.021739854, 0.29476613, -0.08475931, 0.15018553, -0.07886365, 0.36336347, -0.020576432, 0.25866082, -0.059272554, 0.054249667, -0.17822553, 0.1755872, 0.3244387, -0.39173844, 0.33894604) * go_2(0.0, 0.0);\n    result += mat4(-0.11570926, 0.1342677, -0.19511898, 0.0075454637, -0.01890476, -0.14239742, 0.18921931, 0.033990458, 0.31306365, -0.006998358, 0.029190077, -0.005679954, -0.15341778, 0.07766778, -0.25691047, -0.0964161) * go_2(0.0, 1.0);\n    result += mat4(0.019746238, 0.0021332854, -0.00879096, -0.1338671, -0.0001600663, -0.29465106, 0.0867611, -0.114963025, 0.07874301, -0.012734178, -0.11124061, -0.010926616, -0.04941506, -0.07516841, 0.116663, -0.29018974) * go_2(1.0, -1.0);\n    result += mat4(-0.01651721, 0.05955898, 0.023618208, 0.098695934, 0.018553663, -0.054378513, 0.1436929, 0.1693743, -0.27483663, 0.029127488, 0.09619316, -0.06109113, -0.08619361, 0.09315214, -0.02478657, 0.18544984) * go_2(1.0, 0.0);\n    result += mat4(0.09570196, -0.016528936, -0.1559397, 0.14312246, 0.04029428, 0.08773151, -0.043646842, 0.17894371, -0.082413055, 0.0027082344, -0.100171275, 0.01547501, 0.18122818, -0.11933676, 0.26404107, -0.3169703) * go_2(1.0, 1.0);\n    result += mat4(-0.12073344, 0.08683522, -0.09249099, 0.058786053, -0.14480567, -0.121013954, 0.033335857, 0.009353379, -0.055087596, -0.13002734, 0.08890566, 0.05508963, -0.0075715426, -0.15936922, -0.03968994, -0.1690259) * go_3(-1.0, -1.0);\n    result += mat4(0.2011206, 0.23898427, 0.23656492, 0.1287573, 0.14850396, 0.40532517, -0.107408255, 0.40119782, 0.099813245, -0.03830304, 0.101520434, -0.026478073, -0.048469637, 0.106440455, 0.056632314, -0.17825997) * go_3(-1.0, 0.0);\n    result += mat4(-0.076735444, 0.05965795, -0.0052469415, -0.21785147, 0.11887833, 0.067560315, 0.051149055, 0.23626682, -0.1297049, -0.035512198, 0.20352256, -0.025064934, 0.04958706, 0.0454198, 0.0113334535, 0.0417486) * go_3(-1.0, 1.0);\n    result += mat4(-0.09055751, 0.033915352, -0.21836667, 0.22006813, -0.099022895, 0.11720966, -0.15686816, -0.13586599, -0.094427735, -0.08831514, -0.06182928, 0.09213704, -0.03642064, 0.18129414, -0.012926811, 0.12179882) * go_3(0.0, -1.0);\n    result += mat4(0.19389409, 0.09512252, 0.14768016, -0.16623649, -0.031052284, -0.026814984, 0.106168024, -0.2026781, -0.04581419, -0.0016849053, -0.04101923, 0.038959503, -0.011938445, 0.20096186, -0.26666564, 0.4824324) * go_3(0.0, 0.0);\n    result += mat4(0.17727576, 0.07309147, 0.12131863, -0.163096, 0.17225246, 0.26256254, 0.27685758, 0.09094053, 0.029605515, -0.20217367, 0.047564875, 0.043115832, 0.15089568, -0.09670934, 0.24131384, 0.03337442) * go_3(0.0, 1.0);\n    result += mat4(-0.34192136, 0.12063195, -0.31159517, 0.04170889, -0.30147067, -0.21330686, -0.1514457, -0.121126845, 0.04409098, 9.2206596e-05, 0.027680017, 0.03230512, -0.27993527, -0.093485355, 0.07568645, -0.23585452) * go_3(1.0, -1.0);\n    result += mat4(0.0537712, -0.20847629, 0.1740093, -0.013894753, -0.32719997, -0.059484575, -0.006098233, -0.10336451, -0.14706188, -0.07424865, -0.07045905, 0.17093194, -0.22147557, 0.09086218, -0.11033544, -0.05306482) * go_3(1.0, 0.0);\n    result += mat4(0.00489003, -0.11509064, -0.021005848, 0.16637677, -0.089347586, 0.17545725, -0.17313693, 0.13742085, -0.14577347, 0.07951095, -0.092139855, 0.017118992, -0.053472433, 0.079414465, 0.0330263, -0.11189824) * go_3(1.0, 1.0);\n    result += vec4(-0.034743138, 0.012946433, -0.082333155, 0.07721756);\n    return result;\n}\n//!DESC Anime4K-v4.0-Restore-CNN-(VL)-Conv-4x3x3x16\n//!HOOK MAIN\n//!BIND conv2d_3_tf\n//!BIND conv2d_3_tf1\n//!SAVE conv2d_4_tf1\n//!WIDTH conv2d_3_tf.w\n//!HEIGHT conv2d_3_tf.h\n//!COMPONENTS 4\n#define go_0(x_off, y_off) (max((conv2d_3_tf_texOff(vec2(x_off, y_off))), 0.0))\n#define go_1(x_off, y_off) (max((conv2d_3_tf1_texOff(vec2(x_off, y_off))), 0.0))\n#define go_2(x_off, y_off) (max(-(conv2d_3_tf_texOff(vec2(x_off, y_off))), 0.0))\n#define go_3(x_off, y_off) (max(-(conv2d_3_tf1_texOff(vec2(x_off, y_off))), 0.0))\nvec4 hook() {\n    vec4 result = mat4(-0.25835788, 0.050451655, -0.1845038, -0.07232528, 0.1323318, 0.26276684, 0.10842882, -0.083056524, 0.17426784, -0.3594826, 0.2728965, 0.08388844, -0.004007842, 0.020535901, -0.051425606, 0.07750436) * go_0(-1.0, -1.0);\n    result += mat4(-0.11410436, 0.014572361, -0.27057216, -0.023974562, 0.05234827, 0.15328228, -0.17502303, -0.3199359, 0.12188045, -0.095813684, 0.024145132, 0.0856916, -0.027453909, -0.043129764, 0.16971985, 0.021623038) * go_0(-1.0, 0.0);\n    result += mat4(0.06611095, 0.038625732, -0.13717118, -0.04497733, 0.15213469, 0.04770935, 0.0729271, -0.062052976, 0.004571303, 0.035141192, -0.059409596, 0.044652313, 0.17520894, 0.09665589, -0.1479193, 0.06528058) * go_0(-1.0, 1.0);\n    result += mat4(-0.1845968, 0.091479465, -0.09394898, -0.13545018, -0.029501775, -0.21426639, 0.09255898, 0.1257644, 0.20256902, 0.06267267, 0.10378081, 0.13494423, 0.058310498, 0.03642236, -0.16268995, -0.048100803) * go_0(0.0, -1.0);\n    result += mat4(0.2155119, -0.3683131, 0.049449228, -0.20559964, -0.11761922, -0.2518804, -0.020712897, 0.12895772, -0.07543782, 0.5805017, -0.11301444, -0.038493153, -0.06710986, -0.09321189, 0.108671665, -0.03259695) * go_0(0.0, 0.0);\n    result += mat4(0.035307787, 0.108389005, -0.27493554, 0.27029404, 0.25523573, -0.28636125, -0.20766719, -0.008661457, -0.004480811, -0.046390545, -0.16221444, 0.008979624, -0.061375532, 0.035076566, -0.018924266, 0.01380219) * go_0(0.0, 1.0);\n    result += mat4(-0.051922515, -0.12463486, -0.10383422, 0.02220095, -0.1573033, 0.13980615, 0.13248625, -0.16803266, -0.0692132, -0.21552645, 0.13744529, 0.23034313, 0.0052666534, 0.028977966, 0.07720251, -0.06477756) * go_0(1.0, -1.0);\n    result += mat4(-0.14097473, 0.2770271, -0.172289, -0.03000696, -0.028684044, 0.040578447, -0.2290285, 0.082329154, -0.042402364, -0.20926563, 0.08233207, 0.11862443, -0.07038536, -0.02273004, 0.091550544, -0.065856494) * go_0(1.0, 0.0);\n    result += mat4(0.14879914, -0.023923844, -0.23569296, 0.20306346, 0.17502785, 0.28776234, -0.2788995, 0.10012439, -0.05635638, -0.025840463, 0.09222198, 0.118032, 0.08057015, 0.1286071, 0.060189806, -0.052669708) * go_0(1.0, 1.0);\n    result += mat4(0.07076086, -0.15111323, -0.07427972, 0.008372168, -0.17791592, -0.16254742, 0.013961132, -0.0944912, -0.23380096, 0.17377278, -0.09683394, 0.019931393, -0.12042098, 0.0016406325, 0.09393333, -0.06882231) * go_1(-1.0, -1.0);\n    result += mat4(0.21465093, 0.04142968, 0.06840044, -0.37831602, -0.05549571, 0.044905066, -0.07873589, -0.026804, -0.34764197, 0.022487951, -0.077293746, 0.089457795, -0.110094436, 0.24233972, 0.06285107, -0.10851744) * go_1(-1.0, 0.0);\n    result += mat4(0.093270175, 0.084138945, 0.03938272, 0.063565865, -0.010733802, 0.13554469, -0.06650261, 0.033002816, 0.011187271, -0.12821455, 0.20785914, -0.030438649, -0.124710515, -0.022294303, 0.09732408, 0.057609864) * go_1(-1.0, 1.0);\n    result += mat4(-0.12833868, 0.021577539, -0.02700365, 0.11799592, -0.03655647, -0.04225167, 0.11049353, -0.16036157, 0.049277548, -0.033842396, 0.10020137, 0.095509745, 0.08060231, -0.09237418, -0.035598125, -0.035926737) * go_1(0.0, -1.0);\n    result += mat4(-0.32829186, 0.3492363, 0.030671779, -0.12606762, 0.010437313, 0.2757115, -0.21517593, -0.15800527, -0.12592544, -0.20578934, 0.10444053, 0.12993255, -0.046079267, 0.03834173, -0.19277227, -0.22124454) * go_1(0.0, 0.0);\n    result += mat4(-0.052546192, 0.026082167, 0.13831234, 0.10982424, 0.012946818, -0.12439852, 0.10134106, -0.10050398, -0.04472338, -0.14325236, -0.20579574, 0.0044005127, 0.22013672, -0.32955512, 0.12404084, -0.008160738) * go_1(0.0, 1.0);\n    result += mat4(-0.10774314, -0.31650826, -0.06601711, 0.19635755, -0.12622592, -0.06396423, 0.13856032, 0.16540553, 0.021387719, 0.23377723, -0.053738154, -0.1000186, -0.08338395, -0.052813534, 0.008122962, 0.13732094) * go_1(1.0, -1.0);\n    result += mat4(-0.18270823, 0.06966014, -0.17788303, -0.27303055, -0.077971615, 0.013978423, -0.02039098, 0.12715338, -0.11924171, 0.18900296, -0.085199654, 0.215198, 0.18587974, -0.009749325, 0.0173584, -0.12018259) * go_1(1.0, 0.0);\n    result += mat4(0.052129295, -0.107416354, 0.12711766, 0.03708665, -0.14369462, -0.055359814, -0.16639823, -0.045143317, -0.06925672, -0.040696755, 0.01999809, -0.016040625, -0.02484878, 0.07417094, 0.050875198, 0.2145528) * go_1(1.0, 1.0);\n    result += mat4(0.055696912, -0.16680926, -0.021987487, 0.024941636, -0.0927883, 0.022136632, 0.033782948, -0.10646058, -0.14944647, 0.25457275, 0.046682496, -0.022462368, -0.07886781, 0.08165927, 0.06848105, 0.0063734027) * go_2(-1.0, -1.0);\n    result += mat4(0.037053242, 0.033215813, 0.18291366, 0.12340375, 0.08491059, -0.28442004, -0.0127422465, -0.039834313, -0.23321372, 0.26676926, -0.05636355, -0.15672484, -0.12891728, -0.15486577, -0.032004442, -0.092745155) * go_2(-1.0, 0.0);\n    result += mat4(0.015779478, -0.18457565, 0.24996394, 0.036197674, 0.15694007, 0.15863103, -0.07332398, 0.0016235278, -0.15536517, -0.056062788, 0.14102836, 0.16915025, -0.08001087, 0.07073164, 0.13796777, 0.123867124) * go_2(-1.0, 1.0);\n    result += mat4(0.045792986, -0.15135059, -0.1354885, -0.043678258, -0.35655212, 0.51232076, -0.12816145, -0.046569496, -0.014127674, -0.06282611, -0.098873, -0.06359104, -0.0919222, 0.11822437, 0.079254694, 0.00579688) * go_2(0.0, -1.0);\n    result += mat4(-0.15683417, 0.61610246, -0.3024612, 0.12917964, -0.09303367, 0.23612969, -0.40842506, -0.12374661, -0.07572449, -0.2613284, -0.09970177, -0.015227848, 0.106239066, -0.21411185, 0.051998455, -0.1364518) * go_2(0.0, 0.0);\n    result += mat4(0.23850034, -0.14394449, -0.0031468747, -0.2380617, -0.027200876, -0.041352056, -0.01864445, 0.033848196, -0.12064239, -0.110480845, 0.08450956, -0.22328654, 0.17664163, 0.22268307, 0.050886698, -0.17475672) * go_2(0.0, 1.0);\n    result += mat4(-0.17808256, 0.010803805, 0.03315186, 0.033143792, -0.14205995, 0.25039625, -0.08784382, -0.13454252, 0.19576813, 0.10755282, 0.22821628, 0.019456752, -0.0422955, -0.016182603, -0.12066697, 0.0548465) * go_2(1.0, -1.0);\n    result += mat4(0.11563777, -0.257929, 0.0010403778, 0.080267854, -0.0025255163, 0.2855168, -0.060352214, -0.07816255, -0.00090574916, 0.049510725, 0.03720483, 0.059250016, -0.08674136, 0.20522198, -0.28694284, 0.1299507) * go_2(1.0, 0.0);\n    result += mat4(-0.14638457, 0.04063328, 0.03139636, -0.007934521, 0.07689684, -0.09467145, 0.10607347, 0.054510128, 0.003306194, 0.05347124, 0.062762424, -0.041480847, -0.07677865, -0.139573, 0.010972524, 0.21957156) * go_2(1.0, 1.0);\n    result += mat4(-0.026845628, -0.043439507, 0.034738723, 0.07281683, 0.14474197, 0.031586993, -0.22767854, -0.0707655, 0.105201736, -0.28805482, 0.008668302, -0.16329518, 0.06157049, 0.3803886, 0.26345953, -0.011096537) * go_3(-1.0, -1.0);\n    result += mat4(-0.23328833, 0.085731484, -0.07755016, 0.33559516, 0.07704345, 0.115106605, -0.24114038, -0.44630137, 0.2726737, -0.32170138, -0.009236524, -0.11666051, 0.0457048, 0.07876708, 0.13134004, -0.035318643) * go_3(-1.0, 0.0);\n    result += mat4(-0.05140272, 0.011605703, 0.13899171, -0.05071015, 0.18413687, -0.31413674, -0.13043414, -0.15118152, -0.15326938, -0.10720126, -0.23738635, 0.13481396, 0.25115076, -0.009316611, -0.2584441, -0.14389823) * go_3(-1.0, 1.0);\n    result += mat4(-0.039723795, -0.14869407, -0.1692942, 0.026501274, -0.10685166, -0.121267825, -0.08584318, -0.09580693, -0.10626739, -0.068417974, 0.11321909, -0.13664317, 0.061380867, -0.2587898, 0.14850819, 0.008178645) * go_3(0.0, -1.0);\n    result += mat4(0.06912782, 0.24230564, -0.048150286, 0.2203717, -0.17417085, 0.105546735, -0.16648416, -0.0045053074, 0.09764028, 0.37122592, -0.1939995, -0.27899942, -0.088152565, -0.53869057, 0.21676709, -0.08056594) * go_3(0.0, 0.0);\n    result += mat4(0.07651754, 0.03704878, -0.0197015, 0.1660726, 0.07002748, -0.11820414, -0.23360898, 0.1481592, 0.029847002, 0.054057185, 0.013176299, 0.06552942, -0.13865773, -0.20105527, -0.37550658, 0.005769631) * go_3(0.0, 1.0);\n    result += mat4(-0.22697811, -0.17426412, 0.10148018, 0.008134666, 0.10771455, 0.16943407, -0.016319012, -0.40176705, -0.06854668, -0.049045276, 0.20919096, 0.13240765, -0.050125647, 0.14902508, 0.052697595, -0.13817468) * go_3(1.0, -1.0);\n    result += mat4(0.04301619, 0.23184754, -0.023551717, 0.3768405, 0.028999053, 0.06709736, -0.05993663, -0.059861984, 0.15499207, -0.22217415, 0.111131504, -0.09082529, -0.19389243, 0.024621522, -0.15305442, 0.010799284) * go_3(1.0, 0.0);\n    result += mat4(-0.035496738, 0.010802548, -0.028718363, 0.19263634, 0.16900502, -0.16661702, -0.027631328, 0.18309957, -0.015860107, -0.03309961, -0.091390446, 0.14000848, -0.0036591904, 0.47659522, -0.09373507, -0.29020965) * go_3(1.0, 1.0);\n    result += vec4(0.08895955, -0.027667087, 0.20500831, 0.00037762933);\n    return result;\n}\n//!DESC Anime4K-v4.0-Restore-CNN-(VL)-Conv-4x3x3x16\n//!HOOK MAIN\n//!BIND conv2d_4_tf\n//!BIND conv2d_4_tf1\n//!SAVE conv2d_5_tf\n//!WIDTH conv2d_4_tf.w\n//!HEIGHT conv2d_4_tf.h\n//!COMPONENTS 4\n#define go_0(x_off, y_off) (max((conv2d_4_tf_texOff(vec2(x_off, y_off))), 0.0))\n#define go_1(x_off, y_off) (max((conv2d_4_tf1_texOff(vec2(x_off, y_off))), 0.0))\n#define go_2(x_off, y_off) (max(-(conv2d_4_tf_texOff(vec2(x_off, y_off))), 0.0))\n#define go_3(x_off, y_off) (max(-(conv2d_4_tf1_texOff(vec2(x_off, y_off))), 0.0))\nvec4 hook() {\n    vec4 result = mat4(0.018134737, -0.2296755, -0.07276725, -0.029795367, 0.05382051, 0.092847414, -0.024469728, -0.1674685, 0.0017946451, 0.30074653, 0.0034195695, -0.04892261, 0.18229689, -0.20116119, -0.12702174, -0.08259108) * go_0(-1.0, -1.0);\n    result += mat4(-0.1357695, -0.08149211, 0.09314453, -0.21966846, 0.34740716, 0.043606415, 0.04225903, 0.034449834, 0.17248215, 0.39148283, -0.13868807, -0.010550686, 0.044238456, -0.09693464, -0.005044985, 0.24383289) * go_0(-1.0, 0.0);\n    result += mat4(0.19959371, 0.098685324, 0.058746945, 0.010580748, 0.08051514, 0.031898864, 0.017556064, 0.13004355, -0.01727653, 0.11044019, 0.040673427, -0.20064595, -0.23321067, 0.06398686, -0.19126236, -0.2430858) * go_0(-1.0, 1.0);\n    result += mat4(-0.12870286, -0.113455534, 0.23722827, 0.070718594, 0.19049989, -0.1927299, -0.06343845, 0.113127775, 0.082530305, -0.10972526, -0.090779535, 0.05731582, 0.11018802, -0.18049154, 0.09269507, -0.10304576) * go_0(0.0, -1.0);\n    result += mat4(0.15513484, 0.06659583, 0.08125296, -0.012350324, -0.09492788, 0.5048303, 0.13206847, 0.39554298, 0.28953737, -0.20913891, -0.26781562, -0.17539899, 0.023778774, 0.29716817, 0.15768486, 0.37702608) * go_0(0.0, 0.0);\n    result += mat4(0.0724462, 0.015571356, -0.032217246, 0.0050658924, -0.22708446, 0.03968809, 0.016753826, 0.0025668752, -0.055932112, 0.113931604, 0.19766758, -0.030027265, -0.17384295, 0.15013468, -0.0070017707, -0.09469028) * go_0(0.0, 1.0);\n    result += mat4(-0.078361556, -0.0954201, -0.006358101, 0.040500037, 0.4190454, -0.17622913, -0.07234791, 0.05462559, 0.18641087, 0.058313597, -0.0180785, 0.13818781, -0.14640772, 0.0699486, 0.0073663946, -0.076789856) * go_0(1.0, -1.0);\n    result += mat4(-0.21421191, 0.08736062, 0.09041226, 0.03608585, 0.02769972, 0.09641289, 0.11824623, 0.05653645, 0.16464607, 0.19839554, -0.13379547, 0.054417104, 0.067530684, 0.18971571, 0.13785432, -0.097639814) * go_0(1.0, 0.0);\n    result += mat4(-0.32658005, -0.14606023, -0.069448665, 0.032998275, -0.28331423, 0.0011900732, -0.020304207, -0.13535896, 0.08298347, 0.045509677, -0.030503955, -0.037504148, 0.049955815, 0.0925771, 0.00058534974, -0.12398032) * go_0(1.0, 1.0);\n    result += mat4(-0.2955836, 0.29059318, -0.018196672, -0.35866606, -0.01309431, 0.03540315, 0.010609202, 0.11956812, 0.10296229, 0.22536302, 0.015201129, -0.23797737, -0.16960852, -0.11414787, -0.034440614, 0.112644605) * go_1(-1.0, -1.0);\n    result += mat4(-0.14952518, 0.07024436, -0.083184876, -0.0814617, -0.13303639, 0.016159372, -0.13521518, 0.2221334, -0.056617837, 0.12958299, 0.064461656, -0.20146395, -0.16023181, 0.2640758, 0.27528805, -0.1426518) * go_1(-1.0, 0.0);\n    result += mat4(-0.04382363, 0.09856003, -0.08561442, -0.15699928, -0.121069774, 0.04685383, -0.009170197, -0.031489655, 0.18730178, 0.238442, 0.22497098, 0.032015145, -0.03709115, 0.1535079, 0.21674158, 0.10678019) * go_1(-1.0, 1.0);\n    result += mat4(-0.12200952, 0.24224263, 0.034097504, -0.028179523, -0.011962496, -0.04489487, -0.05198827, 0.22194928, -0.045400873, -0.049828544, 0.111477956, -0.098361604, 0.12788995, -0.016093334, -0.19886433, -0.011161484) * go_1(0.0, -1.0);\n    result += mat4(0.30563712, 0.013071727, -0.004799883, 0.12888052, -0.259498, -0.041566677, 0.07311124, 0.162324, 0.28371668, -0.004693743, -0.0019395344, 0.029358242, 0.08730285, 0.12184509, 0.05508437, 0.048439097) * go_1(0.0, 0.0);\n    result += mat4(0.12760857, 0.115813166, -0.217695, -0.10629871, -0.227366, 0.09030426, -0.15313712, 0.020528946, -0.20743734, 0.088583544, 0.04594053, -0.22891994, 0.18949282, -0.042186577, -0.17330512, -0.010711361) * go_1(0.0, 1.0);\n    result += mat4(0.029503195, 0.0063797613, -0.17004286, -0.096844055, 0.010218098, 0.04247233, 0.02362808, 0.14700809, -0.08082364, 0.11159672, -0.018505255, -0.15228583, 0.15693732, -0.025359154, 0.024829186, 0.1943192) * go_1(1.0, -1.0);\n    result += mat4(-0.03912932, -0.21989027, 0.12203028, 0.18702275, -0.118537985, 0.21039696, 0.09102061, 0.012288879, 0.031666897, 0.1318455, -0.04901404, -0.07516063, -0.44782668, 0.04884501, 0.047070876, 0.008728358) * go_1(1.0, 0.0);\n    result += mat4(-0.08669101, 0.3053463, -0.08963947, 0.0034188698, -0.070004664, 0.064788476, 0.093737036, 0.070050925, 0.12728429, -0.13179256, -0.014913502, 0.09308136, -0.027638942, 0.008638711, 0.08794172, -0.05531093) * go_1(1.0, 1.0);\n    result += mat4(0.0728421, 0.07872358, 0.11454748, 0.08497922, 0.071820416, -0.11789207, -0.08184197, 0.1359588, -0.2143346, -0.05876081, 0.023172129, -0.08430511, -0.19276723, 0.14283359, 0.15604696, -0.055187486) * go_2(-1.0, -1.0);\n    result += mat4(0.068641685, 0.2732106, -0.2809107, 0.12736696, -0.08642367, 0.023898933, -0.17859498, -0.18299665, -0.06684587, -0.12204666, 0.45898953, -0.24240111, 0.25182098, -0.04395751, 0.10637211, -0.22135144) * go_2(-1.0, 0.0);\n    result += mat4(0.0852072, 0.051133018, 0.03333165, -0.0008938216, 0.10251267, 0.0550774, 0.041769378, -0.21259712, 0.286912, 0.123342015, 0.282759, -0.0730124, 0.14275575, -0.15580742, -0.15224406, 0.045376908) * go_2(-1.0, 1.0);\n    result += mat4(0.03328225, 0.11563978, -0.07451964, 0.030546209, -0.04698351, -0.18544962, 0.037350416, 0.13969816, 0.0556746, -0.06359919, 0.06478219, -0.031694926, 0.13396506, 0.09443612, -0.01922686, -0.06290365) * go_2(0.0, -1.0);\n    result += mat4(0.07495407, 0.063429266, -0.106221214, -0.085107304, 0.2497817, -0.46598253, -0.18833177, -0.2731128, -0.13024822, 0.56053543, 0.055704467, -0.12331414, -0.031199086, 0.05061188, 0.22097112, -0.6611177) * go_2(0.0, 0.0);\n    result += mat4(0.08276988, -0.044184342, -0.03562185, -0.06159881, 0.27694225, -0.07192965, -0.08663714, 0.020221777, 0.14095962, -0.06229397, 0.051374253, -0.038158998, 0.10664802, -0.041305423, 0.051260717, -0.054698635) * go_2(0.0, 1.0);\n    result += mat4(0.12800686, 0.03485072, 0.039914366, 0.034041498, -0.08305794, -0.046292894, 0.22765331, 0.10904922, 0.0013937047, -0.08750301, 0.009126207, -0.065589435, 0.2837707, 0.08884436, -0.07234862, -0.093502745) * go_2(1.0, -1.0);\n    result += mat4(0.113439895, 0.06081726, 0.1122302, -0.022936966, 0.10329637, -0.31816107, -0.051597945, 0.23846027, -0.083913095, -0.29872265, -0.040147282, -0.08981918, -0.04329814, -0.12339693, -0.034489952, 0.013393211) * go_2(1.0, 0.0);\n    result += mat4(0.33091688, 0.1726297, 0.034332044, -0.091396205, 0.15434311, -0.0022870845, -0.15506189, 0.08710491, -0.16063525, 0.042252056, 0.017086457, 0.08134797, 0.08631321, 0.037843138, 0.088296555, 0.0064518084) * go_2(1.0, 1.0);\n    result += mat4(0.09161051, 0.114355795, -0.15304486, -0.030537153, 0.1835368, -0.3287635, 0.031197926, 0.09717476, 0.04276852, 0.113250345, 0.05949038, -0.10599563, 0.43574792, -0.060788117, 0.18409383, 0.12678055) * go_3(-1.0, -1.0);\n    result += mat4(-0.018356865, -0.0072578182, 0.12020777, -0.013127592, 0.20136636, -0.22984362, 0.06896224, 0.00044982752, 0.008428429, -0.123316936, -0.09989286, 0.078248784, -0.16313677, -0.003020313, -0.46285018, -0.08967125) * go_3(-1.0, 0.0);\n    result += mat4(-0.03451497, -0.10864502, 0.13207638, 0.17194521, 0.0037514758, -0.20222199, -0.12535086, 0.001511977, 0.056294486, -0.2112898, 0.078261316, 0.10118746, -0.044742294, 0.21793383, -0.19927903, -0.21338293) * go_3(-1.0, 1.0);\n    result += mat4(-0.034903776, -0.10167085, 0.031066334, 0.0379958, 0.20532596, -0.17457838, 0.16556816, -0.0021619152, 0.02682665, 0.03396325, -0.059273884, 0.1922813, -0.072151475, -0.010240544, 0.2302027, 0.12385962) * go_3(0.0, -1.0);\n    result += mat4(-0.20170145, -0.08203941, -0.028107846, -0.18003726, 0.44744352, -0.13190243, 0.13233365, 0.03626546, 0.085763134, -0.25613126, -0.11213388, 0.15529087, -0.271649, 0.050587676, -0.062583975, 0.057289865) * go_3(0.0, 0.0);\n    result += mat4(-0.040649455, -0.17949733, 0.35847965, -0.040587306, 0.24314344, -0.23811667, 0.13958354, 0.04961874, 0.09858903, -0.04202913, -0.21850993, 0.0700419, -0.09130745, -0.096835814, 0.0022782686, -0.25416258) * go_3(0.0, 1.0);\n    result += mat4(-0.08215545, -0.019647893, 0.055263475, 0.053733055, 0.098485716, -0.1041945, -0.06541415, -0.08868577, -0.07262986, 0.03513784, -0.110529095, -0.03369232, 0.056786604, 0.2569229, -0.05931065, -0.22081214) * go_3(1.0, -1.0);\n    result += mat4(0.066926084, 0.029664058, -0.10779271, 0.11026963, 0.23927264, -0.16914488, 0.022947345, 0.12303853, -0.07066212, -0.013205378, 0.15348643, 0.035568032, 0.20966691, 0.010149819, -0.08814468, -0.064854674) * go_3(1.0, 0.0);\n    result += mat4(0.11493852, -0.074924305, -0.14840698, -0.16956823, 0.056806292, -0.06387947, -0.06880271, -0.04637334, -0.1929893, 0.18226422, 0.064644486, -0.1594863, 0.027403917, 0.13951495, -0.06569123, -0.07700207) * go_3(1.0, 1.0);\n    result += vec4(-0.043347504, -0.20504741, -0.037821215, -0.014486937);\n    return result;\n}\n//!DESC Anime4K-v4.0-Restore-CNN-(VL)-Conv-4x3x3x16\n//!HOOK MAIN\n//!BIND conv2d_4_tf\n//!BIND conv2d_4_tf1\n//!SAVE conv2d_5_tf1\n//!WIDTH conv2d_4_tf.w\n//!HEIGHT conv2d_4_tf.h\n//!COMPONENTS 4\n#define go_0(x_off, y_off) (max((conv2d_4_tf_texOff(vec2(x_off, y_off))), 0.0))\n#define go_1(x_off, y_off) (max((conv2d_4_tf1_texOff(vec2(x_off, y_off))), 0.0))\n#define go_2(x_off, y_off) (max(-(conv2d_4_tf_texOff(vec2(x_off, y_off))), 0.0))\n#define go_3(x_off, y_off) (max(-(conv2d_4_tf1_texOff(vec2(x_off, y_off))), 0.0))\nvec4 hook() {\n    vec4 result = mat4(0.047881734, -0.09396414, -0.2839081, 0.3140853, 0.052613556, 0.09940423, 0.23960467, -0.022228222, -0.12065009, 0.07898222, 0.08657881, 0.010852739, -0.050450284, 0.01683982, 0.031813968, 0.053060856) * go_0(-1.0, -1.0);\n    result += mat4(-0.10252411, -0.03116448, -0.30114275, -0.0316799, -0.017501019, -0.03006003, -0.2095696, 0.10134927, -0.3901916, -0.15335023, -0.11955071, 0.1337449, 0.101239376, -0.25044814, 0.2128469, 0.018979514) * go_0(-1.0, 0.0);\n    result += mat4(-0.13392173, 0.052036732, 0.1682114, -0.026263753, 0.027221246, -0.15121374, 0.13723798, 0.08950682, -0.1182108, -0.07294226, 0.023392374, 0.052329235, -0.05632852, -0.07036173, 0.06872573, 0.05238042) * go_0(-1.0, 1.0);\n    result += mat4(0.18112028, 0.18242362, -0.06812871, 0.032463413, 0.124638766, -0.26765212, -0.07678663, 0.33806562, 0.09674393, 0.15574542, 0.23634006, -0.02873782, -0.1626769, -0.14760062, -0.007274849, 0.09866139) * go_0(0.0, -1.0);\n    result += mat4(-0.10726673, -0.10925056, 0.19967109, -0.19936769, 0.15942842, -0.14870064, 0.15493345, -0.08489036, -0.49053356, -0.17321263, 0.28426084, 0.18721215, -0.09898434, -0.2751838, -0.11833524, 0.028445128) * go_0(0.0, 0.0);\n    result += mat4(-0.11788817, -0.23724948, -0.046072144, 0.035621114, 0.04527003, -0.0073492974, 0.11097195, 0.06806836, 0.04814677, -0.1408476, -0.1325629, 0.00929532, -0.16699041, -0.03034791, 0.08320368, -0.15429299) * go_0(0.0, 1.0);\n    result += mat4(0.2729515, 0.008244692, -0.17441982, -0.39026466, 0.17381759, 0.31194404, 0.055934936, 0.20744409, 0.20119062, 0.0734271, 0.0796807, 0.0031037466, -0.0016392237, 0.033733975, 0.07149338, 0.042083208) * go_0(1.0, -1.0);\n    result += mat4(0.07985744, 0.10945015, 0.018472541, 0.1397503, 0.2005682, 0.42641, 0.23022486, -0.2916921, 0.028285174, -0.31885162, -0.27070364, -0.10390779, 0.0751492, 0.12752363, -0.2279459, 0.08998453) * go_0(1.0, 0.0);\n    result += mat4(0.18450491, -0.140783, -0.008006845, 0.09029298, 0.12536179, 0.26949662, 0.09491545, 0.063907005, 0.11212244, 0.09778506, -0.1835966, -0.053119674, 0.0072294096, 0.25018227, 0.010868525, -0.22721334) * go_0(1.0, 1.0);\n    result += mat4(-0.028011927, -0.20073172, 0.5976166, -0.19494139, 0.17958745, -0.03838646, 0.058325976, -0.29409218, -0.12793432, 0.03245129, 0.35662368, -0.05048354, -0.13368197, -0.06151968, -0.012714591, -0.1763054) * go_1(-1.0, -1.0);\n    result += mat4(0.18468465, 0.31682113, 0.12818255, -0.117110476, 0.13709468, -0.10034022, -0.07994527, -0.1259309, 0.04067299, -0.1147398, 0.28361055, 0.27916273, 0.03696692, 0.16829546, 0.27819383, 0.08305029) * go_1(-1.0, 0.0);\n    result += mat4(-0.28920117, -0.033877946, 0.01586206, 0.04681198, 0.024248574, -0.045777842, -0.03342128, 0.07525412, -0.063377544, -0.016737273, 0.11235511, -0.04325238, -0.24170023, -0.09993599, -0.03205371, 0.14339828) * go_1(-1.0, 1.0);\n    result += mat4(-0.008357902, -0.11038377, 0.03709221, 0.26775306, 0.07963845, -0.25377446, -0.17630441, -0.10966474, 0.057311732, -0.083327, 0.044497233, 0.06903858, -0.26531395, -0.103399664, -0.14806591, 0.269314) * go_1(0.0, -1.0);\n    result += mat4(0.05450808, -0.041993964, -0.07217651, 0.034468375, 0.2117634, 0.0075620585, 0.05825411, -0.2252478, -0.0527787, 0.049732126, -0.032040413, -0.09361454, 0.29585132, 0.018413153, 0.18384546, -0.024226356) * go_1(0.0, 0.0);\n    result += mat4(-0.031109914, 0.19351351, 0.07405522, -0.06313074, -0.09983541, -0.011495182, 0.11749038, -0.16775608, 0.2790974, -0.09338754, 0.07913264, 0.103792936, -0.18679164, -0.15639925, 0.112943865, 0.07930375) * go_1(0.0, 1.0);\n    result += mat4(0.004106195, -0.036833283, 0.12908752, 0.12869535, -0.02472107, 0.17561707, -0.025890926, -0.18789047, 0.096218705, -0.16306408, -0.02198454, -0.010134957, -0.09710009, 0.002062143, -0.046785697, 0.0029441968) * go_1(1.0, -1.0);\n    result += mat4(0.19648251, -0.015663045, -0.0730215, 0.028611008, 0.13529862, -0.015256192, -0.04119306, -0.24628192, 0.02601027, -0.21184283, -0.1962902, 0.09109358, -0.06792383, 0.092336476, 0.12215351, -0.08596062) * go_1(1.0, 0.0);\n    result += mat4(-0.17530201, -0.0351919, -0.31872514, -0.13933206, -0.07000922, -0.049807087, 0.0010997375, -0.033573963, 0.07442056, -0.33290103, -0.40381998, 0.09435, -0.3280128, -0.09953127, -0.11283648, 0.20685865) * go_1(1.0, 1.0);\n    result += mat4(-0.052573867, -0.035328753, -0.11132943, -0.17515652, 0.05021051, 0.058642425, -0.046640664, 0.0799107, -0.027398815, -0.33619994, -0.22135767, 0.07894002, -0.14941697, -0.0940996, -0.11655085, 0.049795926) * go_2(-1.0, -1.0);\n    result += mat4(-0.039301276, 0.041062318, 0.20312686, -0.009338705, 0.013706282, -0.0245852, 0.03458311, 0.09601228, -0.18203016, -0.012260314, 0.17984508, -0.056576703, -0.102844186, 0.24047872, 0.05307189, 0.16066082) * go_2(-1.0, 0.0);\n    result += mat4(0.1478775, 0.0046362123, 0.05459521, 0.07162838, -0.01896149, 0.23700175, -0.14174299, 0.06988599, -0.32545477, -0.08065096, -0.061227743, -0.0010796773, 0.094327345, -0.20760082, -0.19523263, 0.19859222) * go_2(-1.0, 1.0);\n    result += mat4(-0.049676366, -0.10381536, 0.02546116, -0.13127093, 0.10954914, 0.0048147943, 0.06962328, -0.30456528, -0.11956627, 0.0150488885, -0.10711722, 0.1684613, -0.1939089, -0.10577047, -0.11980919, -0.036988296) * go_2(0.0, -1.0);\n    result += mat4(-0.054795764, 0.09491116, -0.08494948, 0.059765853, 0.0131597435, 0.20786162, 0.11999637, 0.024381055, 0.22830428, 0.027053319, -0.011646274, -0.12145409, -0.07899559, -0.012688263, 0.10684157, 0.3824219) * go_2(0.0, 0.0);\n    result += mat4(-0.23994572, -0.0031532666, -0.0050638164, 0.14236279, 0.05690383, -0.06259682, 0.052624144, 0.20461404, -0.19230312, -0.11072268, 0.013023965, 0.08931543, -0.21997221, 0.11760443, -0.40943825, 0.28656834) * go_2(0.0, 1.0);\n    result += mat4(-0.06606179, 0.26007771, 0.033754125, 0.119690455, 0.024669139, -0.06752839, 0.12688096, -0.0063201943, -0.17123021, 0.07548857, -0.14213699, 0.034093797, -0.15632647, -0.123243414, -0.42634043, 0.1715022) * go_2(1.0, -1.0);\n    result += mat4(-0.046503466, 0.13876389, 0.17973013, -0.25938338, -0.18824704, -0.11876702, 0.31065792, -0.041042212, -0.061369427, 0.2057992, 0.17295738, 0.3836555, -0.21109799, -0.10167118, 0.16577047, 0.113483034) * go_2(1.0, 0.0);\n    result += mat4(-0.24534856, -0.014482421, 0.22515748, -0.12773542, 0.12794174, -0.02528619, 0.41710484, 0.09154934, -0.17805946, -0.25428918, 0.07294183, 0.047079418, -0.30949152, -0.08919157, 0.17888431, 0.17706038) * go_2(1.0, 1.0);\n    result += mat4(-0.1741826, 0.046225294, -0.10761791, 0.2619953, 0.007373745, 0.05104337, -0.22309966, 0.34529984, -0.034363825, -0.022187237, -0.08609555, 0.16842419, 0.28136057, 0.17843607, -0.11307746, -0.05668021) * go_3(-1.0, -1.0);\n    result += mat4(-0.12310616, -0.29661375, -0.10581025, -0.049584012, 0.19651765, 0.08436489, -0.14533581, -0.029874112, -0.15422897, -0.062741704, -0.22694711, -0.15547274, -0.15181333, 0.0286061, 0.022438493, -0.062447168) * go_3(-1.0, 0.0);\n    result += mat4(0.3497046, -0.09455009, 0.060618952, -0.2134236, 0.054515295, 0.07451165, -0.09267233, -0.010513333, 0.13842636, 0.11563433, -0.054750167, 0.050432, 0.1514256, 0.04284002, -0.2095581, 0.07907657) * go_3(-1.0, 1.0);\n    result += mat4(-0.11745651, -0.04717057, 0.085377194, -0.065956995, 0.07280491, 0.2730059, 0.11088276, 0.2437957, 0.14018989, 0.1164107, -0.09516929, 0.0022427947, 0.111544006, -0.0680495, 0.09324579, -0.12482022) * go_3(0.0, -1.0);\n    result += mat4(-0.07995795, -0.03387884, 0.019846136, 0.10231208, -0.07017192, 0.18659039, 0.035161644, 0.101182766, -0.14901665, 0.21307294, 0.063894205, -0.27546507, -0.24792959, -0.067731075, 0.13146006, -0.19333683) * go_3(0.0, 0.0);\n    result += mat4(0.034206454, 0.1472648, -0.07406727, 0.014654025, 0.18703444, 0.1319857, -0.10610886, 0.08427947, -0.017536618, -0.06487879, -0.12095286, -0.050414838, 0.03260879, 0.1558894, -0.031887084, 0.11840288) * go_3(0.0, 1.0);\n    result += mat4(0.114811294, -0.14574333, -0.09392587, 0.042283528, 0.08919092, 0.18259068, 0.0980717, 0.21024778, -0.1280008, -0.027260462, -0.1129027, 0.18722472, 0.13733985, 0.047153983, 0.030871978, 0.1998385) * go_3(1.0, -1.0);\n    result += mat4(-0.06783575, 0.004612595, 0.1153467, -0.11531557, -0.048889533, 0.07673577, -0.02041786, 0.22744459, -0.13092506, 0.13484807, 0.40003043, -0.053706612, -0.16985156, -0.04791236, -0.052443005, -0.08363625) * go_3(1.0, 0.0);\n    result += mat4(0.18187882, 0.017893985, 0.17856054, 0.005413129, 0.014147176, 0.15102178, 0.12436294, -0.02176765, -0.16727823, -0.0364111, 0.17074408, 0.12899421, 0.31984514, -0.0072070034, 0.031895883, -0.1991405) * go_3(1.0, 1.0);\n    result += vec4(-0.011865144, 0.11717201, -0.13823777, -0.059450272);\n    return result;\n}\n//!DESC Anime4K-v4.0-Restore-CNN-(VL)-Conv-4x3x3x16\n//!HOOK MAIN\n//!BIND conv2d_5_tf\n//!BIND conv2d_5_tf1\n//!SAVE conv2d_6_tf\n//!WIDTH conv2d_5_tf.w\n//!HEIGHT conv2d_5_tf.h\n//!COMPONENTS 4\n#define go_0(x_off, y_off) (max((conv2d_5_tf_texOff(vec2(x_off, y_off))), 0.0))\n#define go_1(x_off, y_off) (max((conv2d_5_tf1_texOff(vec2(x_off, y_off))), 0.0))\n#define go_2(x_off, y_off) (max(-(conv2d_5_tf_texOff(vec2(x_off, y_off))), 0.0))\n#define go_3(x_off, y_off) (max(-(conv2d_5_tf1_texOff(vec2(x_off, y_off))), 0.0))\nvec4 hook() {\n    vec4 result = mat4(-0.082203194, 0.021720003, 0.03725474, -0.08048348, 0.2063248, -0.033020593, -0.17585336, 0.06476272, 0.012244563, 0.026554609, 0.014708393, 0.26606125, 0.14248778, 0.12817341, -0.039826933, -0.12751861) * go_0(-1.0, -1.0);\n    result += mat4(0.24573852, 0.19695967, -0.06257417, -0.04782871, 0.3511875, -0.018083302, -0.077342674, 0.15247667, 0.20321761, -0.07479984, -0.09548503, 0.08109568, -0.23808748, 0.07246303, -0.004242619, 0.16162953) * go_0(-1.0, 0.0);\n    result += mat4(0.13296306, 0.19495387, 0.009222276, 0.033592198, 0.20443891, 0.16063854, -0.2581601, -0.016132578, -0.2296461, -0.23647323, -0.15407176, -0.18265317, 0.2343241, -0.049697313, -0.09398783, 0.41931856) * go_0(-1.0, 1.0);\n    result += mat4(-0.10866088, -0.40605694, -0.0042648134, 0.07943803, 0.26914695, 0.14816476, 0.037706107, -0.123223364, -0.19962949, -0.053534556, -0.08397409, -0.04244924, -0.075791344, 0.29629225, 0.2311928, 0.099177904) * go_0(0.0, -1.0);\n    result += mat4(-0.1748319, -0.2003186, -0.32659066, -0.21007413, 0.20122464, 0.032196607, -0.026299698, 0.33395135, 0.11411664, 0.05971959, 0.09001304, -0.15936212, 0.012322024, 0.19936106, -0.411186, -0.08319479) * go_0(0.0, 0.0);\n    result += mat4(-0.07349218, 0.006184436, 0.096199185, -0.050186496, 0.064047046, -0.03813128, -0.057007037, -0.025550695, -0.2863145, -0.008512981, -0.20615962, 0.18009211, 0.008298396, 0.22452813, 0.010843521, 0.20169461) * go_0(0.0, 1.0);\n    result += mat4(0.2691149, 0.059546687, 0.08922005, 0.2252196, 0.30341956, -0.024489028, 0.087045394, -0.03856442, -0.14083561, -0.17683443, 0.14137806, 0.15520614, 0.2073925, -0.19525874, 0.23661858, 0.3098405) * go_0(1.0, -1.0);\n    result += mat4(0.006530723, 0.04180736, -0.04762067, -0.064395495, 0.02396811, -0.13332283, 0.0037775645, 0.026309434, 0.0033065109, -0.08315753, 0.02917419, 0.12330464, 0.22819455, -0.07489677, 0.12829056, -0.097994626) * go_0(1.0, 0.0);\n    result += mat4(-0.09983759, 0.032783493, 0.11085758, 0.08993078, -0.057110567, -0.018973934, -0.14946178, -0.03921629, 0.039757587, 0.015860094, 0.04989561, -0.19634786, 0.04351146, 0.019315343, 0.25972188, 0.17989321) * go_0(1.0, 1.0);\n    result += mat4(-0.04111906, -0.165601, 0.0003682197, -0.056232415, -0.32716644, -0.24015541, -0.057547837, 0.05966729, 0.06854747, 0.03599213, -0.18798864, 0.1183447, 0.014268468, -0.1310834, 0.06415977, -0.19414157) * go_1(-1.0, -1.0);\n    result += mat4(-0.00070661673, 0.17671427, 0.10584568, -0.060910843, -0.104282066, -0.22676118, -0.01907062, 0.24882245, -0.043454725, 0.07691623, -0.48371696, 0.013537671, -0.025488405, 0.061228953, 0.18548754, 0.028671112) * go_1(-1.0, 0.0);\n    result += mat4(-0.0121596735, 0.09595702, -0.08244918, -0.1176173, 0.26773354, -0.021729136, 0.075465776, -0.0928876, 0.12461298, 0.16830076, -0.15302569, 0.113850676, 0.09811088, 0.13006307, 0.24999009, 0.10261325) * go_1(-1.0, 1.0);\n    result += mat4(-0.032246377, 0.038265374, -0.26476422, -0.1442876, -0.19866082, 0.08649541, 0.041478764, 0.11155026, 0.21576422, -0.09572912, -0.11174068, -0.19722937, -0.15801935, 0.29604745, -0.08606268, -0.15532136) * go_1(0.0, -1.0);\n    result += mat4(-0.06315591, 0.16151646, -0.009230362, -0.04341246, 0.09085519, 0.21924476, 0.38044852, 0.193819, 0.16622902, 0.0025134624, -0.22688466, -0.025276015, 0.07714917, 0.16302192, -0.11767101, -0.11086476) * go_1(0.0, 0.0);\n    result += mat4(-0.04170153, 0.001859292, -0.26352355, 0.10982333, -0.031867817, 0.15773517, -0.060263418, 0.11117763, -0.017359972, 0.0127261225, 0.0782802, -0.16908924, 0.080516845, -0.05691526, -0.07530135, -0.14553802) * go_1(0.0, 1.0);\n    result += mat4(0.06112685, -0.032287434, 0.17445667, -0.044935808, -0.11449107, -0.051394563, -0.029589338, -0.14555557, 0.03440661, 0.11035615, -0.17175, -0.14851089, 0.037362, -0.18740481, 0.17278154, 0.18073405) * go_1(1.0, -1.0);\n    result += mat4(-0.27670652, 0.19484822, 0.2609349, 0.1455016, 0.04438468, 0.1449185, 0.11185832, -0.18598269, -0.019846648, 0.11886126, -0.098498635, 0.15737785, 0.011406795, -0.18860829, -0.13705735, 0.17535745) * go_1(1.0, 0.0);\n    result += mat4(-0.30244905, -0.28695273, 0.1146976, 0.21144345, -0.037980128, -0.027679864, -0.13992494, -0.04884521, -0.032023884, -0.07921183, -0.16042095, -0.06935386, -0.06570237, -0.1107404, -0.018163798, 0.22625941) * go_1(1.0, 1.0);\n    result += mat4(-0.07292955, -0.07321777, -0.045146503, -0.33291966, -0.096732594, -0.07203495, 0.33692798, 0.2870733, 0.122160144, -0.076574564, 0.042844944, 0.26448342, 0.07672146, -0.028775277, -0.12088313, 0.15583947) * go_2(-1.0, -1.0);\n    result += mat4(0.21589327, 0.05258274, 0.09705794, -0.024653846, -0.039402515, 0.28485695, 0.14711736, -0.10556087, -0.15140481, 0.09039498, 0.017308712, 0.11862922, 0.08230978, 0.21678248, -0.043815188, -0.226433) * go_2(-1.0, 0.0);\n    result += mat4(-0.029258793, 0.26618922, 0.02564014, -0.23189862, -0.24074338, -0.18556763, 0.25973624, 0.04746873, 0.0137007125, -0.22239363, -0.12414957, 0.048228756, -0.22406264, 0.282667, -0.021001073, -0.17465611) * go_2(-1.0, 1.0);\n    result += mat4(0.32401654, -0.1495363, -0.20869227, 0.04271639, -0.0087802755, 0.031325378, 0.23834595, 0.039336167, 0.17265107, 0.20947595, 0.28737286, 0.0028783784, -0.057340365, -0.050347418, -0.11915604, -0.1831807) * go_2(0.0, -1.0);\n    result += mat4(0.1811338, 0.07732653, 0.20975596, -0.47129005, 0.07121942, 0.08410583, 0.44170937, -0.19524159, -0.17807977, 0.12837476, 0.20816846, -0.1741958, -0.04411918, 0.06024972, 0.18159702, -0.052485272) * go_2(0.0, 0.0);\n    result += mat4(-0.15229738, 0.27513, 0.28150418, -0.19543962, -0.02045864, -0.07207227, 0.09589587, 0.09110817, 0.061413247, 0.0046052113, 0.11619411, -0.2988938, 0.065739445, 0.10205611, 0.12847126, -0.028355654) * go_2(0.0, 1.0);\n    result += mat4(0.0657154, -0.047568597, -0.16148911, 0.16392621, -0.25281775, -0.061153214, 0.017480455, -0.026288848, 0.20319715, 0.04763355, 0.010444491, -0.26671803, -0.25821987, 0.32863674, -0.30734694, -0.18190521) * go_2(1.0, -1.0);\n    result += mat4(-0.042703815, 0.06633036, -0.048434302, -0.17176376, -0.12699759, -0.1124558, 0.083266065, 0.03354623, -0.13468939, 0.12706263, 0.053659134, -0.06930602, 0.008196115, 0.2034998, -0.06351442, -0.039730288) * go_2(1.0, 0.0);\n    result += mat4(0.09614661, 0.22500272, 0.088511504, -0.16960482, 0.15364788, -0.18854137, -0.13163191, -0.07503735, -0.23177068, -0.0053305267, -0.041978605, 0.0971947, -0.049034655, 0.04486706, 0.09076307, -0.02310868) * go_2(1.0, 1.0);\n    result += mat4(-0.1304683, 0.17743458, -0.09817326, -0.0646786, 0.07886976, 0.20109388, -0.034114968, -0.2029261, -0.03348398, 0.029337432, -0.07302782, -0.02240758, 0.030242773, -0.30032325, 0.02085572, -0.027314361) * go_3(-1.0, -1.0);\n    result += mat4(-0.037377544, 0.026350772, -0.07430488, -0.114671774, -0.126935, -0.046512567, -0.033628833, -0.19018382, -0.041053895, -0.031206857, 0.08562848, -0.01875709, 0.21099389, -0.092511, 0.0073047103, -0.009811013) * go_3(-1.0, 0.0);\n    result += mat4(0.11358029, 0.17468451, -0.12739041, -0.14332245, -0.22230148, 0.16862972, -0.04462456, 0.2469604, -0.008622369, 0.0081848325, -0.17032363, -0.16024362, 0.21178265, 0.037127133, 0.08559072, 0.11584694) * go_3(-1.0, 1.0);\n    result += mat4(0.008993893, -0.08037705, 0.4426555, 0.15593371, 0.15273719, -0.03249998, 0.055109, -0.1512612, -0.037183985, 0.20825677, -0.08516227, -0.06664223, -0.10011001, -0.3505215, -0.17941694, 0.052089088) * go_3(0.0, -1.0);\n    result += mat4(-0.109703645, -0.13505603, 0.1336451, 0.13118869, 0.010915504, 0.12748592, 0.21201555, -0.40841985, -0.11059143, 0.033772044, -0.039282143, 0.03095394, 0.10394723, -0.21343367, -0.10699851, -0.028351074) * go_3(0.0, 0.0);\n    result += mat4(0.019704714, 0.06243651, 0.09896519, -0.17492259, 0.012675787, -0.004239029, 0.21319824, 0.069183126, -0.0071114586, 0.123431124, -0.24479835, 0.00723795, -0.045293927, 0.014101029, 0.15746681, 0.042405806) * go_3(0.0, 1.0);\n    result += mat4(0.023828225, -0.0015190929, 0.1194638, 0.082163885, 0.10532113, 0.042044062, 0.02528007, 0.015175004, 0.026613194, 0.33525538, -0.1627064, -0.29887968, -0.197707, 0.038967777, -0.15811683, -0.106895216) * go_3(1.0, -1.0);\n    result += mat4(0.044362027, -0.04946742, -0.14815849, -0.17660522, -0.034201477, -0.012243106, -0.050183997, 0.06407372, 0.039822515, 0.15880872, -0.0672721, -0.4081093, 0.019489579, -0.060278706, -0.015096743, -0.07799167) * go_3(1.0, 0.0);\n    result += mat4(0.11861756, 0.27113584, -0.14107186, -0.10246008, -0.124051, -0.1627854, 0.10698585, 0.2846401, -0.061731786, 0.1724438, -0.12428688, -0.09986041, -0.034171514, -0.07100923, 0.041739646, -0.11308375) * go_3(1.0, 1.0);\n    result += vec4(-0.02981662, -0.26338395, -0.011632586, 0.15063232);\n    return result;\n}\n//!DESC Anime4K-v4.0-Restore-CNN-(VL)-Conv-4x3x3x16\n//!HOOK MAIN\n//!BIND conv2d_5_tf\n//!BIND conv2d_5_tf1\n//!SAVE conv2d_6_tf1\n//!WIDTH conv2d_5_tf.w\n//!HEIGHT conv2d_5_tf.h\n//!COMPONENTS 4\n#define go_0(x_off, y_off) (max((conv2d_5_tf_texOff(vec2(x_off, y_off))), 0.0))\n#define go_1(x_off, y_off) (max((conv2d_5_tf1_texOff(vec2(x_off, y_off))), 0.0))\n#define go_2(x_off, y_off) (max(-(conv2d_5_tf_texOff(vec2(x_off, y_off))), 0.0))\n#define go_3(x_off, y_off) (max(-(conv2d_5_tf1_texOff(vec2(x_off, y_off))), 0.0))\nvec4 hook() {\n    vec4 result = mat4(0.17082009, 0.031344634, -0.06131912, 0.00887183, -0.01528174, 0.12943709, 0.24537678, 0.008178781, -0.312396, -0.023583878, 0.07827866, -0.1231261, 0.15081584, -0.18161978, -0.25179705, -0.036934935) * go_0(-1.0, -1.0);\n    result += mat4(-0.05768411, 0.16785417, -0.1788644, -0.0067257965, 0.021445744, 0.10066516, -0.23864186, 0.1450302, 0.12892793, 0.19856106, -0.24444748, 0.16531628, -0.044425935, -0.02775357, 0.009059946, -0.12958384) * go_0(-1.0, 0.0);\n    result += mat4(-0.025798557, -0.17238182, -0.34056288, -0.20921059, -0.03576266, 0.1476854, -0.06264234, 0.14452787, -0.04130045, -0.07275762, 0.034578666, 0.2914669, 0.20879944, 0.21359251, -0.048695553, 0.2638088) * go_0(-1.0, 1.0);\n    result += mat4(-0.022791177, 0.4204545, 0.116855636, 0.20241925, -0.010444933, -0.14462502, 0.022550104, -0.24423064, -0.09417524, 0.045358784, -0.11405829, 0.035979558, -0.2283092, -0.06670842, -0.23852053, -0.22417003) * go_0(0.0, -1.0);\n    result += mat4(-0.14526704, 0.040880535, 0.14076385, 0.07795045, -0.059177604, -0.13056375, -0.3373641, -0.19344307, -0.29891858, -0.32578763, -0.29061425, 0.1562214, -0.13578376, 0.36586633, 0.24936736, 0.054629393) * go_0(0.0, 0.0);\n    result += mat4(-0.025790233, -0.13020341, -0.10084969, 0.15767297, -0.09738769, 0.04034404, 0.0038675873, 0.043515608, 0.16899958, -0.29117966, 0.03420067, 0.14432564, -0.10473084, 0.21014084, 0.07775908, -0.09303797) * go_0(0.0, 1.0);\n    result += mat4(-0.07443987, -0.16225167, 0.036251917, 0.028432872, 0.03759333, 0.004027401, -0.033941846, 0.0019474924, 0.02357054, 0.30748722, 0.1652115, -0.17361522, 0.16905582, 0.08048018, -0.23639561, -0.029408466) * go_0(1.0, -1.0);\n    result += mat4(0.0461233, -0.09346199, -0.07063276, -0.19447634, -0.049339604, -0.0032855074, -0.22661209, -0.0543389, 0.11924857, -0.21691081, -0.1645725, -0.0075736847, 0.018572787, -0.06552861, -0.01777661, -0.11651732) * go_0(1.0, 0.0);\n    result += mat4(-0.06425901, 0.123392984, -0.16395192, -0.093448035, -0.029316641, 0.0986573, -0.23135012, 0.011170849, 0.00023920486, 0.15296175, 0.35453254, -0.05189021, 0.20708887, -0.103900835, 0.081992395, -0.21829562) * go_0(1.0, 1.0);\n    result += mat4(-0.019074136, -0.1572586, 0.27919227, 0.09119617, 0.035954695, 0.2941489, 0.18262725, -0.055522963, -0.21364328, -0.1573611, 0.104966134, 0.08228523, 0.19945285, -0.0039229114, -0.1565048, 0.028975379) * go_1(-1.0, -1.0);\n    result += mat4(-0.18501253, 0.006473006, 0.06637501, 0.04295065, 0.06411007, 0.1166344, -0.10060226, 0.46296063, -0.08600344, -0.03560105, 0.012215349, 0.017885283, 0.061346993, 0.17336361, 0.01935021, 0.20198092) * go_1(-1.0, 0.0);\n    result += mat4(-0.04451627, -0.10372061, -0.13968691, 0.14479733, 0.1660607, 0.19334625, 0.0085214665, 0.28863636, -0.07600901, -0.014777084, 0.13209191, -0.09045013, 0.104893915, -0.04776884, -0.007936376, 0.104568765) * go_1(-1.0, 1.0);\n    result += mat4(0.023751335, -0.108048, -0.050531313, 0.15916029, 0.13246661, 0.04644228, -0.09586482, -0.17222965, -0.22898191, -0.033484615, 0.078883134, -0.052609313, -0.2721741, 0.045986425, 0.13972299, -0.28923607) * go_1(0.0, -1.0);\n    result += mat4(-0.23364568, -0.008875902, -0.40894926, 0.060443908, -0.2839635, -0.5270991, -0.2500865, 0.002020195, -0.24488612, -0.04982319, -0.009110353, -0.018023955, 0.06647274, -0.25225738, 0.26154432, -0.033934146) * go_1(0.0, 0.0);\n    result += mat4(-0.1535129, -0.21257545, -0.16553773, 0.17471452, -0.06203719, 0.15238857, 0.18702018, 0.18572305, 0.07740396, -0.074217625, -0.072156586, -0.2183728, 0.00403749, 0.13750519, 0.30362993, 0.06550286) * go_1(0.0, 1.0);\n    result += mat4(0.37164542, -0.1980723, -0.15659203, 0.19498909, 0.01748114, 0.011807152, -0.05424202, 0.11926474, 0.050406165, -0.12925303, -0.020280985, 0.08429331, 0.14769496, -0.077555746, -0.15216178, -0.27070466) * go_1(1.0, -1.0);\n    result += mat4(0.35804263, 0.08539285, -0.14785156, -0.13532467, 0.058254432, 0.20448379, -0.006173341, 0.058168225, -0.21714899, -0.13472849, -0.09392532, -0.12753737, -0.097461835, -0.11419082, 0.09384189, 0.06414768) * go_1(1.0, 0.0);\n    result += mat4(0.023494452, -0.22187226, -0.16694295, 0.0204334, -0.26720086, 0.15916729, 0.3098874, -0.10292057, 0.008854983, 0.13375004, -0.04409455, 0.09286524, 0.095829524, 0.12427317, -0.048659876, 0.18300754) * go_1(1.0, 1.0);\n    result += mat4(-0.119153984, 0.10163183, 0.025017537, -0.40096784, 0.026778705, 0.15821172, -0.19947284, -0.33337715, 0.2952563, 0.16820388, -0.057061996, -0.029319009, -0.12184868, 0.09031512, 0.12028806, 0.021044692) * go_2(-1.0, -1.0);\n    result += mat4(0.086744264, -0.046958666, 0.2130253, -0.46672252, 0.07135636, 0.0100029735, -0.13828261, -0.012365689, -0.11374441, 0.21084632, -0.059631422, -0.013799735, -0.037889663, -0.10701892, -0.09493782, 0.15516634) * go_2(-1.0, 0.0);\n    result += mat4(0.031181194, -0.01535001, 0.029270316, 0.13128386, 0.11838377, -0.17051528, 0.12228499, -0.04841128, 0.33350074, -0.006144013, -0.09055018, 0.27470216, -0.26665646, -0.08703671, -0.01719071, -0.23449609) * go_2(-1.0, 1.0);\n    result += mat4(-0.12856458, 0.005562174, -0.19517267, 0.13270985, 0.2776414, 0.032003902, -0.15778573, 0.15344355, 0.26930434, -0.13459459, 0.035019353, 0.08896612, 0.12847935, -0.122637205, 0.001815178, 0.08290523) * go_2(0.0, -1.0);\n    result += mat4(0.33805037, -0.15318587, -0.20955376, -0.26121393, -0.026022578, -0.1617741, 0.1336867, 0.026223289, 0.012059392, -0.17295446, -0.060811974, 0.14027825, -0.21134059, -0.08408573, -0.23773228, 0.110836074) * go_2(0.0, 0.0);\n    result += mat4(0.16176093, 0.15307428, -0.07711325, -0.3458805, 0.061291527, 0.023916256, 0.21370678, 0.0015756418, 0.10642374, 0.24807373, 0.11164451, 0.10780487, 0.087194376, -0.2718231, -0.008457387, 0.054078236) * go_2(0.0, 1.0);\n    result += mat4(-0.03259038, -0.20923306, 0.165477, 0.098864526, -0.02734457, 0.08871225, -0.01552188, 0.047712058, 0.055032052, -0.13044262, -0.2899521, 0.22230095, -0.029343741, -0.16427459, -0.005436118, -0.05111821) * go_2(1.0, -1.0);\n    result += mat4(0.20065974, -0.1556366, -0.12620135, 0.44572976, -0.020925352, 0.12025185, 0.20588058, 0.06391864, 0.046870507, 0.16942503, -0.049370963, 0.008779016, 0.04954915, 0.090298936, -0.16466027, 0.011152038) * go_2(1.0, 0.0);\n    result += mat4(0.13587528, 0.047841422, 0.19804007, -0.1672396, -0.072491, 0.04543739, 0.25287256, 0.015226213, 0.02007356, -0.049578942, -0.08796175, 0.1714897, -0.07819061, 0.1509537, 0.093094915, 0.031139288) * go_2(1.0, 1.0);\n    result += mat4(-0.013774682, 0.118201815, -0.009592314, -0.10837201, -0.0686881, -0.083380274, 0.107689425, 0.046642892, 0.119898744, -0.05502989, -0.19719897, 0.0005697584, -0.0921928, 0.032281205, 0.2568853, 0.2325449) * go_3(-1.0, -1.0);\n    result += mat4(0.02991112, -0.09898633, 0.06076172, -0.20906185, 0.0026118348, 0.06130956, 0.06760944, -0.16662054, 0.065741204, -0.13144116, 0.011419801, 0.22552124, 0.1465757, -0.07417319, -0.10788749, -0.24952699) * go_3(-1.0, 0.0);\n    result += mat4(-0.19238451, -0.024058497, 0.19580396, -0.067399554, -0.18832864, -0.11752747, -0.078949094, -0.23762032, -0.04141864, 0.022530237, -0.02222157, 0.0054874527, 0.057746816, -0.34854797, 0.028730657, -0.08976777) * go_3(-1.0, 1.0);\n    result += mat4(0.16888975, 0.19949849, -0.08456147, -0.03619044, -0.019596824, 0.11214634, 0.13971676, 0.22926724, 0.03219445, -0.04566354, -0.14948955, -0.22817011, -0.08714846, -0.19684613, 0.15479128, 0.2433362) * go_3(0.0, -1.0);\n    result += mat4(0.16050309, -0.102841675, 0.20855242, -0.011171905, -0.10309409, 0.22455123, 0.15892951, -0.06582373, 0.010079549, -0.2055006, -0.09385158, 0.006519388, 0.11838815, 0.37134558, -0.165772, 0.12704434) * go_3(0.0, 0.0);\n    result += mat4(0.11643292, 0.03294274, -0.09800525, -0.13601723, -0.081318736, -0.059975546, -0.039105035, -0.2893635, -0.13024913, -0.058016162, -0.09961072, 0.10532414, 0.24250132, -0.35546342, -0.092634924, 0.093994915) * go_3(0.0, 1.0);\n    result += mat4(-0.18799333, 0.25611782, 0.014645917, -0.063751906, 0.06498416, 0.16619027, -0.14411639, 0.3914421, -0.07343631, -0.116468735, -0.10941946, -0.2553544, -0.37774643, -0.0018441634, 0.06827239, -0.0122299045) * go_3(1.0, -1.0);\n    result += mat4(-0.11884597, -0.2477297, 0.048488285, -0.06438257, -0.124703035, 0.25932777, 0.0650111, -0.0930877, 0.06463341, -0.000544085, 0.0147504965, -0.170097, -0.13241997, 0.20983136, -0.15956205, 0.03424298) * go_3(1.0, 0.0);\n    result += mat4(-0.034574904, 0.06755256, 0.09508443, -0.17162292, 0.046379335, 0.2178781, 0.08699012, -0.055380464, -0.2237568, -0.07427848, -0.028395249, -0.3225617, -0.084454566, -0.24776657, 0.254169, 0.13229847) * go_3(1.0, 1.0);\n    result += vec4(0.18765923, -0.07697714, 0.028134674, -0.060966115);\n    return result;\n}\n//!DESC Anime4K-v4.0-Restore-CNN-(VL)-Conv-4x3x3x16\n//!HOOK MAIN\n//!BIND conv2d_6_tf\n//!BIND conv2d_6_tf1\n//!SAVE conv2d_7_tf\n//!WIDTH conv2d_6_tf.w\n//!HEIGHT conv2d_6_tf.h\n//!COMPONENTS 4\n#define go_0(x_off, y_off) (max((conv2d_6_tf_texOff(vec2(x_off, y_off))), 0.0))\n#define go_1(x_off, y_off) (max((conv2d_6_tf1_texOff(vec2(x_off, y_off))), 0.0))\n#define go_2(x_off, y_off) (max(-(conv2d_6_tf_texOff(vec2(x_off, y_off))), 0.0))\n#define go_3(x_off, y_off) (max(-(conv2d_6_tf1_texOff(vec2(x_off, y_off))), 0.0))\nvec4 hook() {\n    vec4 result = mat4(0.21919365, 0.36627784, 0.12603314, 0.24306288, 0.06447028, 0.06472204, -0.05997039, -0.15651788, 0.017059859, -0.006497198, -0.4189735, 0.021636713, -0.23887977, -0.014220949, 0.031113686, -0.17342716) * go_0(-1.0, -1.0);\n    result += mat4(-0.10818789, -0.03273837, 0.33918005, -0.19290088, 0.0955361, -0.34107623, -0.054906327, -0.18083344, -0.060723677, 0.24395694, 0.112975016, -0.07254578, -0.14389384, 0.13235968, -0.15054801, -0.26216486) * go_0(-1.0, 0.0);\n    result += mat4(-0.23442148, -0.07857079, 0.022283873, -0.2656417, 0.037092753, -0.037313666, -0.5057047, 0.042533103, -0.120424, 0.00021930189, -0.0044566668, -0.45536995, 0.00040759926, 0.14597592, -0.094990164, -0.036161344) * go_0(-1.0, 1.0);\n    result += mat4(0.15024352, 0.19903262, -0.0734784, 0.092836305, -0.025753846, 0.024750374, -0.07550193, 0.035420835, 0.11084378, 0.26119822, -0.08443512, -0.0047807065, -0.042685136, 0.24889739, 0.098650105, 0.2088369) * go_0(0.0, -1.0);\n    result += mat4(-0.25551823, 0.14455976, 0.19886157, -0.23465924, 0.20711218, -0.20875362, -0.11320392, -0.30852005, -0.06795657, 0.008670962, 0.30601278, 0.6929064, 0.17079145, 0.15744895, 0.06441601, 0.06514001) * go_0(0.0, 0.0);\n    result += mat4(0.03142604, -0.006410137, -0.023654792, -0.05708553, 0.062985405, -0.077010594, 0.078804865, 0.050882503, 0.010274228, -0.15558401, 0.09490256, 0.14964707, -0.11966925, -0.36176664, 0.27809814, -0.18862294) * go_0(0.0, 1.0);\n    result += mat4(0.05609992, 0.0041612233, -0.08498908, 0.04479823, -0.080117956, -0.17423204, -0.22858045, 0.054569032, -0.050866384, -0.020000307, 0.027000953, -0.67724514, 0.16240878, -0.04641204, 0.0648367, -0.20613132) * go_0(1.0, -1.0);\n    result += mat4(0.08542306, -0.08254248, -0.11090553, -0.14140448, -0.10788511, -0.13011602, -0.29319742, -0.26007155, 0.11033401, -0.31966573, 0.32668245, 0.19542319, 0.06329418, 0.20904626, 0.2724067, -0.009155685) * go_0(1.0, 0.0);\n    result += mat4(-0.007403411, 0.0012836396, -0.23446666, -0.03017208, 0.062420018, -0.13611084, -0.2975928, 0.13173148, -0.03679939, 0.13743873, -0.10121899, 0.074514665, 0.1497629, -0.09523838, 0.39018926, 0.37807035) * go_0(1.0, 1.0);\n    result += mat4(0.11441487, -0.19565523, -0.25757137, -0.16148767, 0.15575317, -0.12657928, 0.10479676, 0.062919036, 0.010544159, 0.22931573, 0.20360178, 0.4637635, -0.3395036, -0.52467215, 0.08759308, 0.028030418) * go_1(-1.0, -1.0);\n    result += mat4(0.2699195, -0.34218305, 0.15259695, 0.03139074, -0.024053533, -0.029567484, 0.28480124, 0.20525953, 0.15452823, -0.217713, 0.15861876, -0.012275699, 0.21408023, 0.097508304, -0.57126766, -0.14679857) * go_1(-1.0, 0.0);\n    result += mat4(-0.0755847, -0.09751562, -0.29480466, -0.22285318, 0.14196442, 0.114573136, -0.22294767, 0.12463806, 0.3322209, -0.04631724, -0.11097061, -0.27986854, -0.16099304, -0.060079545, 0.00299308, 0.120776065) * go_1(-1.0, 1.0);\n    result += mat4(0.050933484, -0.13776319, -0.18809728, 0.24035202, -0.32528606, -0.41684148, -0.029342847, 0.28642926, -0.07963454, -0.12905268, 0.07606093, 0.24670005, -0.08815598, -0.23320907, -0.008099349, 0.21512873) * go_1(0.0, -1.0);\n    result += mat4(0.19247563, 0.18083979, -0.09719762, 0.15314941, -0.22350982, 0.46515045, -0.3571128, 0.35953265, 0.06921985, -0.4482386, -0.18732521, -0.5043983, 0.35159567, -0.33315298, -0.21884166, -0.16283798) * go_1(0.0, 0.0);\n    result += mat4(-0.021124054, -0.007966742, 0.0052493825, 0.022550896, 0.030403977, 0.3377868, -0.47602004, -0.077664234, -0.07222509, -0.07486097, -0.37971064, -0.5107857, -0.06299477, 0.04930232, -0.3330487, 0.29845512) * go_1(0.0, 1.0);\n    result += mat4(-0.063705474, -0.07917637, -0.02026607, -0.05142568, 0.021577014, -0.07379867, 0.033937998, 0.08148773, -0.02717838, -0.03233838, 0.098000035, 0.036476444, -0.13366953, 0.014477577, 0.24064232, 0.39313284) * go_1(1.0, -1.0);\n    result += mat4(-0.16046515, -0.094624564, 0.35435164, 0.09942324, -0.07137174, -0.27999225, 0.124644354, -0.0062176553, 0.015016751, -0.05500243, -0.23249559, -0.4508382, 0.1860433, 0.10671491, -0.033345353, -0.06611453) * go_1(1.0, 0.0);\n    result += mat4(0.21614046, -0.01307525, -0.18941112, -0.20533535, -0.14481686, -0.47801897, 0.22605121, -0.20298961, -0.06744227, -0.20377496, -0.11926173, 0.15645133, -0.31570885, -0.3495616, -0.024666889, 0.040965475) * go_1(1.0, 1.0);\n    result += mat4(-0.11748018, -0.039976366, -0.00084064255, -0.028653437, -0.16216733, -0.036768105, 0.018064514, -0.0928936, 0.14008482, -0.064511225, 0.24329947, -0.0268608, 0.050330248, 0.08540601, -0.07272679, -0.01187671) * go_2(-1.0, -1.0);\n    result += mat4(-0.09459936, -0.011723822, -0.06952858, -0.07808506, -0.065588176, 0.332501, -0.0120042395, 0.07668016, 0.14735217, -0.14856043, -0.06702449, -0.020953184, -0.023006834, 0.06135422, 0.1491448, -0.028061569) * go_2(-1.0, 0.0);\n    result += mat4(0.25136968, 0.25146323, -0.108277924, -0.20407207, -0.0013780294, 0.16108194, 0.25143847, 0.06672421, -0.033905584, -0.021144686, -0.019152718, 0.34619498, 0.14560962, 0.034437314, 0.024790365, -0.049976267) * go_2(-1.0, 1.0);\n    result += mat4(-0.24928351, 0.12637813, 0.23609994, 0.12722939, -0.036997862, -0.16554876, 0.11144095, -0.10040036, -0.020359103, -0.080701865, -0.3142192, 0.27257237, 0.13546956, -0.14416885, 0.028196262, -0.2886465) * go_2(0.0, -1.0);\n    result += mat4(0.28524777, -0.4236231, 0.27420738, -0.21095508, 0.23475651, 0.115876295, -0.18837357, -0.0260708, 0.030670704, -0.11516913, -0.11365572, -0.2203149, -0.018612983, -0.10719593, -0.031727783, 0.1403327) * go_2(0.0, 0.0);\n    result += mat4(0.07240512, 0.03139215, 0.12328737, -0.021201206, -0.13971715, 0.072742075, -0.0011289873, 0.0053133667, 0.035639685, -0.04322272, -0.19288473, -0.15812221, -0.19126481, 0.0698514, 0.17619178, -0.035605464) * go_2(0.0, 1.0);\n    result += mat4(-0.18552057, 0.07259671, 0.011667668, -0.15630563, 0.11414356, 0.14482655, -0.04021029, 0.18495587, -0.11386139, -0.09058561, -0.011265998, 0.23358451, 0.0521358, 0.12495261, 0.021644838, -0.048094347) * go_2(1.0, -1.0);\n    result += mat4(-0.09222373, 0.0533347, 0.055820454, 0.22382596, 0.18713981, 0.2668916, -0.019384036, 0.012698582, 0.13325234, 0.20361474, -0.33106443, -0.08571572, -0.21243028, -0.10996386, 0.123459645, 0.1534967) * go_2(1.0, 0.0);\n    result += mat4(0.18133277, 0.18108074, -0.05638664, 0.29533157, -0.2108019, -0.033636626, 0.5015888, -0.15116066, -0.041320793, -0.14764231, 0.07314567, -0.18865979, 0.10276937, 0.094240844, -0.1364283, 0.27812913) * go_2(1.0, 1.0);\n    result += mat4(0.06040915, 0.23753685, 0.19019844, 0.23948252, -0.07535012, 0.11848904, 0.14389765, 0.050067905, 0.16150077, -0.030053454, 0.12478255, 0.26020208, 0.111198805, 0.06787492, -0.12771018, 0.006687384) * go_3(-1.0, -1.0);\n    result += mat4(-0.5421617, 0.10414128, -0.21526064, -0.08883624, 0.13145073, -0.29695904, 0.57386386, 0.073361695, -0.09538372, 0.27593842, 0.070922814, 0.21769938, 0.06214975, 0.11847816, 0.10033405, 0.29360098) * go_3(-1.0, 0.0);\n    result += mat4(-0.16294672, -0.014815565, 0.22046989, 0.16858687, 0.058917344, 0.21384977, 0.18803519, 0.105688855, 0.0355118, 0.20571202, -0.07341922, 0.26624045, -0.0415102, 0.050942056, 0.19727907, 0.20122413) * go_3(-1.0, 1.0);\n    result += mat4(-0.020470422, 0.15815964, -0.13437317, -0.1967045, 0.074902646, 0.08356444, 0.055913117, -0.12837863, -0.18647918, 0.07002247, 0.038864706, -0.07288784, 0.04135125, -0.016055549, -0.1340297, -0.15578008) * go_3(0.0, -1.0);\n    result += mat4(-0.07685624, 0.00079105416, -0.068755336, 0.110282525, -0.014170752, 0.041282844, -0.17035173, 0.19439398, -0.3036256, 0.024148455, -0.19566648, -0.06736254, 0.14203559, -0.13016985, -0.32845357, -0.14266774) * go_3(0.0, 0.0);\n    result += mat4(0.0087252045, 0.098839566, -0.08770506, -0.08499465, 0.015245115, -0.110854514, 0.054458305, -0.018121868, -0.09666134, -0.08316006, 0.24617113, -0.17195955, 0.2574254, 0.06734342, -0.13792352, -0.07306126) * go_3(0.0, 1.0);\n    result += mat4(-0.0073954533, -0.20126835, -0.22545357, -0.29462856, 0.057408337, 0.11939119, -0.01846476, 0.12534486, 0.15751605, -0.14282645, -0.14219986, 0.14283386, 0.14090413, 0.10500912, 0.03039335, 0.17448832) * go_3(1.0, -1.0);\n    result += mat4(0.043910783, -0.09140025, -0.21666165, 0.07616939, 0.104454786, 0.309926, -0.12906921, 0.1140117, 0.09372434, 0.049547072, -0.086615674, -0.034449168, 0.096705064, 0.26001686, 0.027063297, 0.12422948) * go_3(1.0, 0.0);\n    result += mat4(0.1365422, 0.2679611, 0.12037257, 0.43346113, 0.08223084, -0.016788265, 0.13570398, -0.017974345, -0.17922844, -0.09475725, 0.073539585, -0.106947675, 0.08998511, 0.04133868, 0.16586913, -0.26291734) * go_3(1.0, 1.0);\n    result += vec4(-0.19233678, 0.016725872, -0.008011114, -0.1977463);\n    return result;\n}\n//!DESC Anime4K-v4.0-Restore-CNN-(VL)-Conv-4x3x3x16\n//!HOOK MAIN\n//!BIND conv2d_6_tf\n//!BIND conv2d_6_tf1\n//!SAVE conv2d_7_tf1\n//!WIDTH conv2d_6_tf.w\n//!HEIGHT conv2d_6_tf.h\n//!COMPONENTS 4\n#define go_0(x_off, y_off) (max((conv2d_6_tf_texOff(vec2(x_off, y_off))), 0.0))\n#define go_1(x_off, y_off) (max((conv2d_6_tf1_texOff(vec2(x_off, y_off))), 0.0))\n#define go_2(x_off, y_off) (max(-(conv2d_6_tf_texOff(vec2(x_off, y_off))), 0.0))\n#define go_3(x_off, y_off) (max(-(conv2d_6_tf1_texOff(vec2(x_off, y_off))), 0.0))\nvec4 hook() {\n    vec4 result = mat4(-0.36016628, 0.019064043, 0.3073228, 0.16891135, 0.026739368, 0.31136194, 0.11260383, -0.26918694, 0.0419928, -0.3365078, 0.20189743, -0.04136312, 0.039564647, 0.033199426, 0.18768296, -0.017119858) * go_0(-1.0, -1.0);\n    result += mat4(0.28663483, -0.41716507, 0.059281543, 0.043736435, 0.0028875466, 0.13817391, -0.12543318, -0.2794053, -0.023528943, 0.10610115, 0.09100278, 0.040132936, -0.21949205, -0.027810011, -0.0301218, 0.084047124) * go_0(-1.0, 0.0);\n    result += mat4(0.39674807, -0.0040878756, -0.038235947, 0.11880838, 0.009898328, 0.19107847, -0.009313831, -0.1554276, -0.047341663, 0.18049581, -0.029317195, 0.0708909, 0.0708316, -0.110617444, 0.14584038, -0.022261223) * go_0(-1.0, 1.0);\n    result += mat4(-0.20400241, 0.0896492, -0.010386381, -0.052133385, 0.005023956, -0.06628705, -0.16436209, -0.25345984, -0.05285192, 0.09706557, -0.03778914, -0.152546, 0.17023252, 0.063713826, 0.00743037, 0.056634087) * go_0(0.0, -1.0);\n    result += mat4(-0.080793336, 0.4204207, 0.19098237, 0.20028038, -0.054076545, 0.22064368, -0.25853387, -0.3643562, 0.2085573, -0.023731, -0.06727709, -0.18683033, -0.18032159, -0.06388348, 0.304463, -0.2517781) * go_0(0.0, 0.0);\n    result += mat4(0.11940941, 0.10624008, 0.16120581, 0.2369602, 0.3321827, 0.4272075, -0.10403669, -0.31388018, -0.006372124, -0.00653671, 0.109810196, 0.2277172, 0.005771998, 0.086026914, -0.08934813, -0.094941735) * go_0(0.0, 1.0);\n    result += mat4(-0.13233568, 0.24112508, -0.0068006413, 0.12466225, 0.11396591, -0.07249253, -0.29090378, -0.12828146, -0.22001141, -0.08532405, -0.11932601, 0.29452974, 0.09572195, 0.017603843, 0.12454017, 0.16321751) * go_0(1.0, -1.0);\n    result += mat4(0.042107448, -0.00807216, 0.06580674, -0.1289527, 0.13977426, -0.037159685, -0.21001346, -0.08698161, 0.22370502, -0.29170328, 0.2179206, 0.36621302, 0.0825477, -0.016513655, -0.11157249, 0.12861598) * go_0(1.0, 0.0);\n    result += mat4(0.2246826, -0.13262233, 0.12131653, -0.15522355, 0.38104856, 0.030237729, 0.1286289, -0.19770473, -0.16175011, -0.13688888, 0.23505463, 0.21333031, 0.76352316, -0.17949077, -0.13124311, 0.1613879) * go_0(1.0, 1.0);\n    result += mat4(-0.050607495, 0.0846705, -0.06136092, -0.033436477, 0.41138348, 0.037043408, -0.02676336, -0.37771952, 0.22147503, 0.06490757, -0.04266158, -0.22606373, 0.045775007, -0.054498192, -0.21495876, -0.036050417) * go_1(-1.0, -1.0);\n    result += mat4(-0.06242522, 0.2700824, -0.05602621, -0.12361551, 0.14477442, 0.19403581, 0.23505251, -0.072234035, -0.15831544, 0.4640447, -0.104754634, -0.004539681, -0.20246096, 0.23216484, -0.35886365, 0.11360777) * go_1(-1.0, 0.0);\n    result += mat4(0.14777757, 0.18951412, 0.027219458, 0.11216015, 0.02997997, -0.13466355, -0.0010830094, 0.021302953, 0.23441231, -0.14529245, 0.08068729, 0.10044398, 0.3972878, 0.26570204, 0.0046810666, -0.2863261) * go_1(-1.0, 1.0);\n    result += mat4(-0.10385485, 0.1053724, 0.16961229, 0.20727012, -0.025148917, -0.011365095, 0.03899919, -0.030950211, 0.079080455, -0.32767853, 0.064670205, -0.035771385, 0.16833797, -0.21567492, 0.30871257, -0.19965471) * go_1(0.0, -1.0);\n    result += mat4(-0.23420888, -0.004894698, -0.18162623, -0.31107524, 0.11976508, 0.14924951, -0.08723316, 0.21401922, -0.58200324, -0.01177345, -0.049033508, 0.19593577, -0.21139073, 0.13016601, 0.08734843, 0.4158892) * go_1(0.0, 0.0);\n    result += mat4(0.0009789813, 0.33274913, 0.017405733, -0.042906318, -0.26410276, -0.09291333, 0.019387102, 0.105381854, -0.009176527, 0.09483514, -0.28462934, -0.03644404, 0.285194, -0.4260311, 0.14902237, -0.115670316) * go_1(0.0, 1.0);\n    result += mat4(-0.09344311, 0.4463103, 0.19984834, -0.09733857, -0.118717775, -0.0708026, 0.24919955, -0.11234634, 0.1246395, -0.052909933, 0.1525815, 0.07724016, 0.0070534665, -0.06404165, -0.18149726, -0.014058336) * go_1(1.0, -1.0);\n    result += mat4(-0.17353044, 0.15376104, 0.004588994, -0.13554202, -0.19920237, -0.18918681, 0.11327512, -0.117296435, -0.0785251, 0.013677155, -0.2103214, 0.06843426, -0.27790928, 0.09837545, -0.00019213746, 0.09132539) * go_1(1.0, 0.0);\n    result += mat4(-0.01586651, 0.014929441, 0.2426186, -0.1889374, -0.0865462, -0.07454513, -0.20797268, -0.22366855, 0.19704159, 0.0048206006, -0.16707218, -0.14162683, 0.036798395, -0.1663155, -0.12009389, 0.09603803) * go_1(1.0, 1.0);\n    result += mat4(-0.041532192, 0.05753804, 0.17927068, -0.042112097, 0.12080969, -0.15052572, -0.34855765, -0.07356988, -0.28199884, -0.18958664, 0.15879883, 0.08511588, 0.0034213227, -0.05338495, -0.37285298, 0.06626709) * go_2(-1.0, -1.0);\n    result += mat4(-0.20219134, 0.22150375, -0.29405454, 0.06597703, -0.018885285, -0.010551704, -0.010774283, 0.08758955, -0.2015349, -0.17006227, -0.24321876, -0.06864207, -0.118437864, -0.043977212, -0.029736811, 0.14040919) * go_2(-1.0, 0.0);\n    result += mat4(-0.18709077, -0.09723938, 0.12783436, -0.15167634, 0.29039705, -0.11009911, 0.018371418, -0.060096707, -0.07256923, -0.25799567, -0.06276934, -0.035992302, -0.06729111, -0.059956793, -0.024079734, 0.011838878) * go_2(-1.0, 1.0);\n    result += mat4(0.010449175, -0.08212451, 0.1409803, 0.11861122, -0.18035835, 0.051930565, 0.01049551, -0.09447962, 0.12029649, 0.040604513, -0.059971705, -0.0044667358, -0.22080486, -0.11187681, 0.124374695, -0.004155485) * go_2(0.0, -1.0);\n    result += mat4(-0.28584236, -0.38480133, -0.13987814, -0.4463469, -0.3890419, -0.022498172, 0.17334452, 0.21895568, -0.15450422, -0.10905497, 0.15111905, -0.22554915, 0.106121585, -0.029144369, 0.36059046, 0.22140682) * go_2(0.0, 0.0);\n    result += mat4(-0.23780307, -0.023033705, 0.068205886, -0.110635854, -0.26720005, -0.1608183, 0.19523881, 0.07972837, -0.018495852, -0.2793956, 0.17668398, -0.12020479, -0.079556085, -0.02284952, 0.031480275, 0.31818348) * go_2(0.0, 1.0);\n    result += mat4(0.22501226, -0.00829407, 0.059581667, 0.16512989, 0.18711442, 0.1200968, 0.11812652, -0.16091056, 0.15733972, 0.045156084, 0.20640492, -0.16852027, -0.11217177, 0.06746273, -0.050218176, 0.08643783) * go_2(1.0, -1.0);\n    result += mat4(0.20715691, -0.1082907, 0.027892975, 0.19515261, -0.17838904, 0.1532257, -0.108409844, -0.06632365, -0.13805026, 0.23020233, 0.12416581, -0.14861803, 0.16650471, 0.08158386, -0.09051303, -0.06981649) * go_2(1.0, 0.0);\n    result += mat4(-0.04617126, 0.06579221, 0.25964734, 0.28500968, 0.07641255, -0.090885855, -0.0972522, 0.18298368, -0.06393334, 0.103463, -0.23062052, -0.15270731, 0.13633437, 0.074707486, 0.15065335, -0.024602572) * go_2(1.0, 1.0);\n    result += mat4(0.118319295, 0.010410938, 0.044655934, -0.104725905, 0.030477569, 0.12867387, 0.039075315, 0.18922117, 0.13301082, -0.1601557, 0.038168408, -0.07372259, -0.09522213, -0.095107146, -0.16679631, 0.044673234) * go_3(-1.0, -1.0);\n    result += mat4(0.46229, -0.30780822, -0.09081465, 0.1433387, -0.0315039, 0.059409115, -0.24948491, -0.17146957, 0.060843736, -0.041989822, 0.054005735, 0.22835566, 0.12036598, -0.0070898845, 0.17276852, -0.17754094) * go_3(-1.0, 0.0);\n    result += mat4(-0.35119572, 0.020034311, 0.08751943, 0.08193488, 0.041884877, 0.22649358, -0.07447533, 0.20845473, -0.04859846, -0.16206735, 0.06819576, -0.053000778, 0.18146423, 0.04694148, 0.045293212, 0.06783575) * go_3(-1.0, 1.0);\n    result += mat4(0.280914, -0.14998704, -0.23485807, -0.015608296, 0.1549556, -0.11992663, -0.094974115, 0.05887284, 0.053392075, 0.10322464, -0.075066686, 0.068358354, -0.18663338, 0.009901499, -0.123370335, -0.12502703) * go_3(0.0, -1.0);\n    result += mat4(0.7748568, -0.17870626, -0.20770052, 0.024692526, -0.056430295, -0.06324113, -0.03660047, 0.29629672, -0.51896983, -0.027231261, 0.05903762, 0.077677645, -0.061675485, -0.20277846, 0.10352223, -0.08198446) * go_3(0.0, 0.0);\n    result += mat4(-0.06347568, 0.21643166, -0.09718546, 0.0372257, -0.029537952, -0.0357135, -0.09548363, 0.18225233, -0.29609334, -0.3496132, 0.18245913, -0.10162589, -0.18189451, -0.09077887, 0.117313184, -0.06863874) * go_3(0.0, 1.0);\n    result += mat4(-0.047373574, -0.020289376, -0.25748715, -0.13568166, 0.15656634, -0.06841899, 0.012100781, -0.13611819, 0.0016357322, -0.23870537, 0.14035743, -0.14700134, 0.2535575, -0.13697346, -0.13693139, -0.10365287) * go_3(1.0, -1.0);\n    result += mat4(0.4283934, -0.316192, -0.012617617, 0.018468965, 0.21436644, 0.18408814, -0.42651537, 0.12504087, -0.13894933, 0.091662176, -0.20096369, -0.080727175, -0.005487846, 0.17046383, 0.1383948, -0.0054956395) * go_3(1.0, 0.0);\n    result += mat4(0.20014295, -0.027282396, -0.06317007, 0.04452042, 0.064600386, 0.072222926, -0.33409226, 0.08063831, -0.022607977, 0.1308856, -0.39691743, -0.094889864, -0.1810531, 0.011367248, -0.2531222, -0.22468317) * go_3(1.0, 1.0);\n    result += vec4(0.26886886, 0.05874665, 0.10268232, 0.05833081);\n    return result;\n}\n//!DESC Anime4K-v4.0-Restore-CNN-(VL)-Conv-3x1x1x112\n//!HOOK MAIN\n//!BIND MAIN\n//!BIND conv2d_1_tf\n//!BIND conv2d_1_tf1\n//!BIND conv2d_2_tf\n//!BIND conv2d_2_tf1\n//!BIND conv2d_3_tf\n//!BIND conv2d_3_tf1\n//!BIND conv2d_4_tf\n//!BIND conv2d_4_tf1\n//!BIND conv2d_5_tf\n//!BIND conv2d_5_tf1\n//!BIND conv2d_6_tf\n//!BIND conv2d_6_tf1\n//!BIND conv2d_7_tf\n//!BIND conv2d_7_tf1\n//!SAVE MAIN\n//!WIDTH conv2d_1_tf.w\n//!HEIGHT conv2d_1_tf.h\n#define g_0 (max((conv2d_1_tf_tex(conv2d_1_tf_pos)), 0.0))\n#define g_1 (max((conv2d_1_tf1_tex(conv2d_1_tf1_pos)), 0.0))\n#define g_2 (max(-(conv2d_1_tf_tex(conv2d_1_tf_pos)), 0.0))\n#define g_3 (max(-(conv2d_1_tf1_tex(conv2d_1_tf1_pos)), 0.0))\n#define g_4 (max((conv2d_2_tf_tex(conv2d_2_tf_pos)), 0.0))\n#define g_5 (max((conv2d_2_tf1_tex(conv2d_2_tf1_pos)), 0.0))\n#define g_6 (max(-(conv2d_2_tf_tex(conv2d_2_tf_pos)), 0.0))\n#define g_7 (max(-(conv2d_2_tf1_tex(conv2d_2_tf1_pos)), 0.0))\n#define g_8 (max((conv2d_3_tf_tex(conv2d_3_tf_pos)), 0.0))\n#define g_9 (max((conv2d_3_tf1_tex(conv2d_3_tf1_pos)), 0.0))\n#define g_10 (max(-(conv2d_3_tf_tex(conv2d_3_tf_pos)), 0.0))\n#define g_11 (max(-(conv2d_3_tf1_tex(conv2d_3_tf1_pos)), 0.0))\n#define g_12 (max((conv2d_4_tf_tex(conv2d_4_tf_pos)), 0.0))\n#define g_13 (max((conv2d_4_tf1_tex(conv2d_4_tf1_pos)), 0.0))\n#define g_14 (max(-(conv2d_4_tf_tex(conv2d_4_tf_pos)), 0.0))\n#define g_15 (max(-(conv2d_4_tf1_tex(conv2d_4_tf1_pos)), 0.0))\n#define g_16 (max((conv2d_5_tf_tex(conv2d_5_tf_pos)), 0.0))\n#define g_17 (max((conv2d_5_tf1_tex(conv2d_5_tf1_pos)), 0.0))\n#define g_18 (max(-(conv2d_5_tf_tex(conv2d_5_tf_pos)), 0.0))\n#define g_19 (max(-(conv2d_5_tf1_tex(conv2d_5_tf1_pos)), 0.0))\n#define g_20 (max((conv2d_6_tf_tex(conv2d_6_tf_pos)), 0.0))\n#define g_21 (max((conv2d_6_tf1_tex(conv2d_6_tf1_pos)), 0.0))\n#define g_22 (max(-(conv2d_6_tf_tex(conv2d_6_tf_pos)), 0.0))\n#define g_23 (max(-(conv2d_6_tf1_tex(conv2d_6_tf1_pos)), 0.0))\n#define g_24 (max((conv2d_7_tf_tex(conv2d_7_tf_pos)), 0.0))\n#define g_25 (max((conv2d_7_tf1_tex(conv2d_7_tf1_pos)), 0.0))\n#define g_26 (max(-(conv2d_7_tf_tex(conv2d_7_tf_pos)), 0.0))\n#define g_27 (max(-(conv2d_7_tf1_tex(conv2d_7_tf1_pos)), 0.0))\nvec4 hook() {\n    vec4 result = mat4(0.09689336, 0.06046458, 0.072598994, 0.0, 0.11994565, 0.104477674, 0.09302802, 0.0, -0.05718302, 0.050438102, 0.08814741, 0.0, 0.0308889, 0.0033925986, -0.01715605, 0.0) * g_0;\n    result += mat4(-0.028314235, 0.06597744, 0.0966897, 0.0, 0.035656154, 0.07770106, 0.075551905, 0.0, 0.0001793458, -0.000479495, -0.00297406, 0.0, -0.053916585, -0.016807461, -0.0057141334, 0.0) * g_1;\n    result += mat4(-0.047189303, -0.0207, -0.020910334, 0.0, -0.07933196, -0.06961211, -0.086069845, 0.0, 0.0943727, 0.008463375, 0.010755166, 0.0, 0.062410597, 0.022625161, 0.04068433, 0.0) * g_2;\n    result += mat4(0.10270994, -0.019080428, 0.0050091282, 0.0, -0.004672948, -0.013966742, -0.0063746064, 0.0, -2.5856789e-05, 0.03151499, -0.0023983798, 0.0, 0.113539025, 0.12381699, 0.100360274, 0.0) * g_3;\n    result += mat4(0.07868885, -0.030913834, -0.009213676, 0.0, 0.04870991, 0.021467991, 0.038739506, 0.0, -0.042969644, -0.07122453, -0.08798675, 0.0, -0.09784122, 0.021434791, 0.02510374, 0.0) * g_4;\n    result += mat4(0.050420716, 0.0729716, 0.076532185, 0.0, -0.019112485, -0.01037939, -0.026948035, 0.0, -0.02591423, 0.008927897, -0.00042541025, 0.0, 0.1043701, -0.0071186824, -0.041817162, 0.0) * g_5;\n    result += mat4(-0.16143242, -0.0009298223, -0.01228508, 0.0, 0.07744052, -0.018313263, -0.0488145, 0.0, 0.09241393, 0.07128674, 0.055164956, 0.0, 0.054884013, -0.04834418, -0.06281626, 0.0) * g_6;\n    result += mat4(-0.049036566, -0.05979936, -0.05594288, 0.0, -0.014564307, 0.031926468, 0.037857566, 0.0, 0.015474487, -0.11385003, -0.11527764, 0.0, -0.07076006, 0.057038613, 0.095983796, 0.0) * g_7;\n    result += mat4(0.03094887, -0.008734403, 0.00042712069, 0.0, 0.053891554, 0.05837673, 0.06200635, 0.0, 0.09071558, -0.04202184, -0.046172567, 0.0, -0.0425916, 0.04905093, 0.020835675, 0.0) * g_8;\n    result += mat4(0.096628904, -0.037792254, -0.043241944, 0.0, -0.011923947, -0.025950424, -0.031381752, 0.0, -0.060941868, -0.07859433, -0.07535451, 0.0, -0.026777223, 0.08604982, 0.07829908, 0.0) * g_9;\n    result += mat4(-0.06435972, 0.0036599538, 0.00786578, 0.0, -0.061972067, -0.05681472, -0.06667608, 0.0, -0.106890626, 0.007406496, 0.029977169, 0.0, -0.20519382, -0.044860814, 0.0021225857, 0.0) * g_10;\n    result += mat4(-0.16876474, 0.012789643, 0.026692612, 0.0, 0.017817136, 0.026935097, 0.02227043, 0.0, 0.01690181, 0.07716103, 0.086527, 0.0, 0.07923805, -0.10443151, -0.10859543, 0.0) * g_11;\n    result += mat4(0.003730466, -0.024648283, -0.022169832, 0.0, -0.0062762927, 0.022062732, 0.032966793, 0.0, 0.016349113, 0.017197203, 0.020952817, 0.0, -0.1763789, 0.035497356, 0.053835396, 0.0) * g_12;\n    result += mat4(0.020886675, -0.07054202, -0.079142675, 0.0, 0.06664387, 0.044960167, 0.042230908, 0.0, -0.095019594, 0.012421141, 0.0142890485, 0.0, 0.056814816, -0.012751135, -0.014684506, 0.0) * g_13;\n    result += mat4(0.011765893, 0.0008920681, -0.0018258415, 0.0, -0.010473814, -0.023085753, -0.028783914, 0.0, -0.023034256, -0.0024786016, -0.0052162083, 0.0, 0.1643386, -0.06132718, -0.09289065, 0.0) * g_14;\n    result += mat4(0.016597198, 0.09389637, 0.10833379, 0.0, -0.043163072, -0.04714812, -0.035274632, 0.0, 0.09634976, -0.009292612, -0.022424143, 0.0, -0.08765172, 0.0051558353, 0.010900356, 0.0) * g_15;\n    result += mat4(0.030815786, 0.021069322, 0.01812191, 0.0, 0.084839165, -0.0080813095, -0.029270556, 0.0, -0.10456346, 0.062386703, 0.0665605, 0.0, 0.11926609, -0.1104228, -0.13291118, 0.0) * g_16;\n    result += mat4(-0.07159541, -0.007267032, -0.010134558, 0.0, 0.008234213, 0.045609634, 0.040295456, 0.0, 0.018416971, 0.01308482, 0.014649557, 0.0, 0.035107512, -0.02140815, -0.030279048, 0.0) * g_17;\n    result += mat4(0.01918586, 0.03875863, 0.03229402, 0.0, -0.07917104, 0.041135103, 0.057182517, 0.0, 0.08609541, 0.0079662455, 0.004327576, 0.0, -0.14332893, 0.03120354, 0.056732506, 0.0) * g_18;\n    result += mat4(0.03200192, -0.0035752193, -0.0031064528, 0.0, -0.010902813, 0.014607456, 0.019431474, 0.0, -0.016461229, -0.004938204, -0.004655488, 0.0, -0.033470232, 0.0026075812, 0.005896968, 0.0) * g_19;\n    result += mat4(0.037410006, 0.048742272, 0.04348088, 0.0, 0.037719514, 0.030768529, 0.03127472, 0.0, 0.056426726, 0.03066893, 0.016440205, 0.0, -0.010599352, 0.022832409, 0.023211194, 0.0) * g_20;\n    result += mat4(-0.005733291, 0.06365659, 0.06663611, 0.0, -0.041917093, -0.016493445, -0.020438088, 0.0, -0.0014357592, -0.0022506563, -0.0045095007, 0.0, 0.029893145, -0.009129354, -0.015173116, 0.0) * g_21;\n    result += mat4(0.013052085, 0.005108175, 0.0025906067, 0.0, -0.021950055, -0.036447693, -0.036141638, 0.0, -0.036296472, 0.0068928464, 0.013102313, 0.0, 0.0060471976, -0.024798103, -0.023548538, 0.0) * g_22;\n    result += mat4(0.0067743887, -0.06191211, -0.062355213, 0.0, 0.0016080744, -0.020445071, -0.016840393, 0.0, 0.028264903, 0.01852915, 0.015891539, 0.0, -0.023877412, -0.013271666, -0.008158679, 0.0) * g_23;\n    result += mat4(-0.04317466, -0.018953001, -0.020452993, 0.0, -0.009322576, -0.03022352, -0.030970376, 0.0, 0.05653658, 0.05430553, 0.046692245, 0.0, 0.05615359, 0.059338935, 0.056018773, 0.0) * g_24;\n    result += mat4(0.022878079, 0.03392234, 0.033057988, 0.0, -0.017554542, -0.0141542535, -0.014122613, 0.0, -0.048634093, -0.05316463, -0.047988772, 0.0, -0.058002178, -0.040221967, -0.034025013, 0.0) * g_25;\n    result += mat4(-0.018253656, -0.04197674, -0.040467236, 0.0, -0.04358929, -0.028309818, -0.025425073, 0.0, -0.008488672, -0.001727991, 0.00035808363, 0.0, -0.0011709273, 0.0052514165, 0.0059479307, 0.0) * g_26;\n    result += mat4(-0.08333935, -0.09818201, -0.09476284, 0.0, -0.033692095, -0.046259012, -0.045797516, 0.0, -0.007577072, 0.0022402718, 0.0016200038, 0.0, 0.0029786075, -0.020728534, -0.018938033, 0.0) * g_27;\n    result += vec4(0.047567394, -0.02504617, -0.028163986, 0.0);\n    return result + MAIN_tex(MAIN_pos);\n}\n"
  },
  {
    "path": "app/src/main/assets/shaders/Anime4K_Upscale_CNN_x2_M.glsl",
    "content": "// MIT License\n\n// Copyright (c) 2019-2021 bloc97\n// All rights reserved.\n\n// Permission is hereby granted, free of charge, to any person obtaining a copy\n// of this software and associated documentation files (the \"Software\"), to deal\n// in the Software without restriction, including without limitation the rights\n// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell\n// copies of the Software, and to permit persons to whom the Software is\n// furnished to do so, subject to the following conditions:\n\n// The above copyright notice and this permission notice shall be included in all\n// copies or substantial portions of the Software.\n\n// THE SOFTWARE IS PROVIDED \"AS IS\", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR\n// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,\n// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE\n// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER\n// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,\n// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE\n// SOFTWARE.\n\n//!DESC Anime4K-v3.2-Upscale-CNN-x2-(M)-Conv-4x3x3x3\n//!HOOK MAIN\n//!BIND MAIN\n//!SAVE conv2d_tf\n//!WIDTH MAIN.w\n//!HEIGHT MAIN.h\n//!COMPONENTS 4\n//!WHEN OUTPUT.w MAIN.w / 1.200 > OUTPUT.h MAIN.h / 1.200 > *\n#define go_0(x_off, y_off) (MAIN_texOff(vec2(x_off, y_off)))\nvec4 hook() {\n    vec4 result = mat4(-0.010995803, 0.077095956, -0.043992598, 0.06048717, 0.1164834, -0.11689607, 0.072985925, -0.078805886, 0.01182932, 0.054985743, -0.09018186, 0.044907484, 0.0, 0.0, 0.0, 0.0) * go_0(-1.0, -1.0);\n    result += mat4(0.1813623, -0.14752422, 0.025720436, -0.17639883, 0.15697388, 0.10445984, -0.1843076, 0.5264643, 0.047516696, -0.097305484, 0.09740847, -0.29619336, 0.0, 0.0, 0.0, 0.0) * go_0(-1.0, 0.0);\n    result += mat4(-0.014534763, 0.09486465, 0.046173926, 0.039391946, 0.09609376, -0.060574662, 0.042200956, -0.3269777, 0.051006425, 0.059818447, 0.04366627, 0.17699827, 0.0, 0.0, 0.0, 0.0) * go_0(-1.0, 1.0);\n    result += mat4(0.04268535, -0.08152529, 0.10577459, -0.036936995, -0.051562306, 0.054872766, 0.09194519, 0.0025066638, -0.01073954, 0.00064474024, 0.10038221, 0.02131141, 0.0, 0.0, 0.0, 0.0) * go_0(0.0, -1.0);\n    result += mat4(-0.51751363, -0.40028602, 0.3469574, 0.5933738, -0.91357684, -0.67692596, 0.57815677, 0.39809322, -0.16341521, -0.27169713, 0.12232366, 0.4318641, 0.0, 0.0, 0.0, 0.0) * go_0(0.0, 0.0);\n    result += mat4(0.12601124, -0.06263236, -0.45907676, -0.41514075, 0.3330334, -0.1929565, -0.6333532, -0.6552794, -0.045809917, 0.046351526, -0.26173338, -0.30252662, 0.0, 0.0, 0.0, 0.0) * go_0(0.0, 1.0);\n    result += mat4(0.0030332592, 0.012103107, 0.010537323, -0.02038607, 0.095558085, 0.097704545, 0.083433494, 0.026790185, 0.01943357, -0.061712462, -0.00015703632, -0.032268334, 0.0, 0.0, 0.0, 0.0) * go_0(1.0, -1.0);\n    result += mat4(0.016870102, 0.5215812, -0.11525501, 0.027527615, -0.09045733, 0.61310345, -0.1575268, 0.1905386, 0.020172214, 0.3503187, -0.08209157, -0.051328037, 0.0, 0.0, 0.0, 0.0) * go_0(1.0, 0.0);\n    result += mat4(0.005494087, -0.010656317, 0.07682753, -0.08116042, -0.03934524, 0.16589017, 0.101483546, -0.066603065, 0.03494657, -0.07885597, 0.074227594, 0.0016264897, 0.0, 0.0, 0.0, 0.0) * go_0(1.0, 1.0);\n    result += vec4(0.014463938, -0.0031906287, 0.007015422, -0.003888468);\n    return result;\n}\n//!DESC Anime4K-v3.2-Upscale-CNN-x2-(M)-Conv-4x3x3x8\n//!HOOK MAIN\n//!BIND conv2d_tf\n//!SAVE conv2d_1_tf\n//!WIDTH conv2d_tf.w\n//!HEIGHT conv2d_tf.h\n//!COMPONENTS 4\n//!WHEN OUTPUT.w MAIN.w / 1.200 > OUTPUT.h MAIN.h / 1.200 > *\n#define go_0(x_off, y_off) (max((conv2d_tf_texOff(vec2(x_off, y_off))), 0.0))\n#define go_1(x_off, y_off) (max(-(conv2d_tf_texOff(vec2(x_off, y_off))), 0.0))\nvec4 hook() {\n    vec4 result = mat4(-0.08532478, -0.14302494, -0.017921071, -0.0032664281, -0.09841952, 0.024187077, 0.10701477, 0.14110753, -0.05714981, -0.10897174, 0.073803626, 0.103992954, 0.07914382, 0.032193683, -0.18346278, -0.09723936) * go_0(-1.0, -1.0);\n    result += mat4(-0.034482613, -0.10742312, -0.047286414, -0.08641124, -0.33896688, -0.036533825, -0.48337597, 0.034040943, -0.13598205, -0.080917805, 0.08540263, -0.012667689, -0.009171425, -0.120026454, -0.20536867, -0.032149274) * go_0(-1.0, 0.0);\n    result += mat4(0.18687321, 0.066278316, 0.024327392, 0.08816582, -0.08017908, 0.09488853, 0.26018232, -0.101504356, 0.17487666, 0.31057635, 0.14785016, -0.09622089, -0.07537452, -0.13844088, -0.05810814, 0.09907489) * go_0(-1.0, 1.0);\n    result += mat4(-0.04183032, 0.15207712, 0.005002397, 0.32277516, -0.16169126, -0.119836345, -0.04068436, -0.096728764, 0.11943901, 0.1789597, -0.20412198, 0.19009817, 0.36630696, 0.06946421, -0.5254373, -0.11896399) * go_0(0.0, -1.0);\n    result += mat4(-0.31916487, -0.98911583, 1.0728644, -0.39280394, 0.33458877, -0.17325239, -0.645045, -0.28524077, -0.14512783, 0.24996442, -0.09837877, 0.05468934, 0.31559715, -0.020504637, -0.026724018, 0.24507573) * go_0(0.0, 0.0);\n    result += mat4(-0.23759829, -0.08530173, -0.16665787, -0.22463752, 0.109896734, 0.13446991, -0.049552456, -0.02385489, -0.01245375, 0.3833208, 0.05758832, 0.1528937, 0.0501858, -0.19651426, 0.0076587177, -0.03297025) * go_0(0.0, 1.0);\n    result += mat4(0.14554465, -0.01826686, 0.10284085, -0.19152659, -0.017585073, -0.05511482, 0.06362406, 0.023924058, -0.0018977845, -0.103172876, 0.03287086, -0.20085956, 0.36062446, 0.10749464, -0.20984372, 0.018256644) * go_0(1.0, -1.0);\n    result += mat4(-0.005534592, 0.3709197, -0.18287498, 0.1720451, 0.030155553, -0.023265475, 0.0058617783, -0.031765483, 0.037328955, -0.2730994, 0.35090837, -0.3269043, -0.028477207, 0.32756507, -0.15989502, 0.12158258) * go_0(1.0, 0.0);\n    result += mat4(0.10873739, 0.19583772, 0.060394943, 0.09410379, -0.04739245, 0.026561242, 0.022990001, 0.1093272, -0.01071349, -0.022938967, -0.046423864, 0.2385325, -0.0319821, 0.046962265, 0.09081178, -0.11001857) * go_0(1.0, 1.0);\n    result += mat4(0.13012704, 0.112289295, 0.030790284, -0.050499484, 0.11784853, 0.08107028, -0.07556717, -0.15643, 0.015249331, 0.015299608, 0.07748125, 0.054485757, 0.044857923, 0.12161275, -0.048292994, -0.033995003) * go_1(-1.0, -1.0);\n    result += mat4(0.12931514, 0.15114146, 0.070513315, 0.11246343, 0.4142387, 0.213479, -0.5439916, 0.07776645, 0.13109331, 0.2021147, 0.25932786, -0.22157331, 0.02377734, -0.014970623, -0.1943276, 0.18440372) * go_1(-1.0, 0.0);\n    result += mat4(-0.22365458, -0.19829084, -0.06881161, -0.06468993, 0.17202774, 0.0048758537, -0.09235021, 0.18941896, 0.064125344, -0.09067088, 0.09748182, 0.13561936, -0.05876288, -0.0122420965, -0.054380875, -0.17743628) * go_1(-1.0, 1.0);\n    result += mat4(0.18582906, -0.09263032, -0.08210888, -0.20515606, 0.11484005, 0.08557595, 0.0009253741, -0.051202174, -0.18535301, -0.1529345, -0.13092944, 0.03770747, -0.020947013, 0.19187425, -0.15494856, -0.048979875) * go_1(0.0, -1.0);\n    result += mat4(-0.38131633, 0.4278787, 0.19763695, 0.27655518, -0.08711912, 0.07374453, -0.064803004, 0.5983854, 0.2361923, -0.057221692, -0.37138999, -0.24259573, 0.13890724, 0.25706333, -0.54021406, 0.08095518) * go_1(0.0, 0.0);\n    result += mat4(0.0991328, -0.022651536, -0.029148921, -0.009812537, -0.09523686, -0.15704902, 0.052389514, 0.21561539, 0.1950314, -0.08572602, 0.0016523858, 0.14125621, -0.030999828, 0.12009709, 0.0373512, -0.105043754) * go_1(0.0, 1.0);\n    result += mat4(-0.11251988, 0.12106985, 0.011923068, 0.3662747, 0.004800994, 0.017972551, 0.004761366, -0.07934206, -0.13755941, -0.022852683, 0.1502225, 0.009758547, -0.16964264, 0.00984782, 0.07855833, 0.035730787) * go_1(1.0, -1.0);\n    result += mat4(0.01964957, -0.27226487, 0.033933397, -0.117632054, -0.009058229, 0.047830686, -0.01125145, 0.136628, 0.0056388285, 0.3028781, -0.12286517, 0.23498532, -0.009319075, -0.444048, 0.16174883, -0.06367683) * go_1(1.0, 0.0);\n    result += mat4(0.02343933, -0.010915871, -0.058680378, -0.21886891, -0.010750894, -0.06671997, 0.0602906, -0.07903071, 0.066891186, 0.06650588, 0.14362891, -0.101870626, 0.02264628, -0.06940821, -0.077616625, 0.110911585) * go_1(1.0, 1.0);\n    result += vec4(0.032014452, -0.020821465, 0.0826416, -0.002838458);\n    return result;\n}\n//!DESC Anime4K-v3.2-Upscale-CNN-x2-(M)-Conv-4x3x3x8\n//!HOOK MAIN\n//!BIND conv2d_1_tf\n//!SAVE conv2d_2_tf\n//!WIDTH conv2d_1_tf.w\n//!HEIGHT conv2d_1_tf.h\n//!COMPONENTS 4\n//!WHEN OUTPUT.w MAIN.w / 1.200 > OUTPUT.h MAIN.h / 1.200 > *\n#define go_0(x_off, y_off) (max((conv2d_1_tf_texOff(vec2(x_off, y_off))), 0.0))\n#define go_1(x_off, y_off) (max(-(conv2d_1_tf_texOff(vec2(x_off, y_off))), 0.0))\nvec4 hook() {\n    vec4 result = mat4(-0.06963679, -0.07560548, -0.069522075, 0.0038078027, -0.08002613, 0.13671301, 0.084461786, -0.039376218, 0.19136548, -0.123174496, 0.26566333, -0.16583005, -0.18664864, -0.023539122, -0.21928434, -0.026818147) * go_0(-1.0, -1.0);\n    result += mat4(0.16660932, -0.18558703, 0.37230486, 0.118128106, -0.14098641, 0.14659132, -0.22217897, 0.12952235, -0.4139033, -0.04308319, 0.12885277, -0.17986743, -0.23556231, -0.08351981, -0.43240538, 0.019033253) * go_0(-1.0, 0.0);\n    result += mat4(-0.18008037, -0.04448665, 0.011906908, -0.023056917, 0.18136618, -0.04723555, -0.0050158803, -0.14823224, -0.2105281, 0.023047728, -0.14040631, -0.03178526, -0.13477588, -0.01820428, 0.058358394, 0.23792502) * go_0(-1.0, 1.0);\n    result += mat4(0.07363309, -0.061728477, 0.03573137, -0.0050971056, -0.012813505, -0.17236637, 0.1697835, 0.055788577, -0.22263195, 0.10324512, 0.58971673, -0.4872246, -0.1555681, 0.032747746, -0.096495196, 0.070196226) * go_0(0.0, -1.0);\n    result += mat4(0.14174286, 0.099460006, -0.088765986, 0.58350676, -0.025177564, -0.46004987, 0.37007022, -0.11437029, -0.5164534, -0.60465246, 0.38859612, -0.32846406, 0.050266482, -0.20334712, 0.18316261, -0.19327633) * go_0(0.0, 0.0);\n    result += mat4(-0.09377763, -0.0012762006, -0.028991895, -0.26523829, 0.20173682, 0.037923716, -0.03174243, 0.07103378, -0.10764164, -0.30752546, 0.20556998, -0.1892279, 0.08115748, -0.023550175, -0.07627362, 0.11746628) * go_0(0.0, 1.0);\n    result += mat4(-0.06998859, -0.017997518, 0.069938794, -0.14943017, -0.14179112, 0.16643842, -0.110231474, 0.08895815, -0.24074875, 0.3277253, -0.07435203, -0.23452802, 0.039962552, -0.07145652, -0.022511544, -0.04571222) * go_0(1.0, -1.0);\n    result += mat4(-0.059785757, -0.23771374, -0.030571314, 0.25222278, 0.106601834, 0.34398326, 0.14511436, -0.03867526, -0.38982397, -0.11944689, 0.12997924, -0.13079585, 0.005729482, 0.012653905, -0.063693404, 0.09632285) * go_0(1.0, 0.0);\n    result += mat4(-0.04933823, 0.0547175, 0.050636575, -0.10060694, 0.1344485, 0.19752938, -0.100068115, -0.028829506, -0.14096203, -0.079092234, 0.092109434, 0.011606209, -0.04052607, -0.008347507, 0.06956573, -0.028109524) * go_0(1.0, 1.0);\n    result += mat4(0.21918017, -0.11115073, 0.2262453, -0.06889667, -0.11256312, -0.07438075, -0.088454485, 0.13672407, -0.06905764, 0.08128395, 0.016103368, 0.050190717, 0.09691516, 0.05845721, 0.4886816, 0.041121427) * go_1(-1.0, -1.0);\n    result += mat4(-0.3449472, 0.09711974, -0.13881907, -0.018265123, 0.27855873, -0.07030004, 0.29545054, 0.37216932, 0.08657718, 0.099066615, -0.10574013, -0.17667885, -0.14855732, -0.11351448, 0.66945946, 0.11312157) * go_1(-1.0, 0.0);\n    result += mat4(0.2526151, -0.04594331, -0.06606611, 0.09104881, 0.06857995, -0.075284235, -0.17664689, 0.21578754, 0.0696524, 0.09142951, 0.080997564, -0.0682772, -0.0011445724, -0.11736295, 0.2519232, -0.101926275) * go_1(-1.0, 1.0);\n    result += mat4(-0.12913518, 0.058357026, 0.195421, -0.15651494, 0.2877076, 0.0033844314, -0.07831594, 0.052855384, -0.031295884, 0.03301088, -0.18408822, 0.06732994, 0.23742151, -0.12568143, 0.22810535, -0.11545694) * go_1(0.0, -1.0);\n    result += mat4(-0.49203303, -0.22656603, 0.1723193, -0.51250046, -0.09742038, 0.758559, -0.3387505, -0.6193586, 0.14136684, 0.27679884, -0.050113205, 0.31041816, -0.36475047, -0.48746544, 0.3233227, 0.4579754) * go_1(0.0, 0.0);\n    result += mat4(0.46636763, 0.1507748, -0.2581362, 0.15413165, -0.17160143, 0.14256273, -0.074575804, -0.099299066, -0.0017214464, 0.13778336, -0.07378213, -0.15489665, -0.10533715, -0.0011083825, 0.39584312, 0.0023906573) * go_1(0.0, 1.0);\n    result += mat4(0.026959421, -0.06391859, 0.0034752619, 0.14521928, -0.0010877338, -0.032619733, 0.005375293, -0.018952755, 0.03381545, -0.007652831, 0.034141563, 0.046016496, 0.11219674, 0.030913852, 0.077403754, 0.17192438) * go_1(1.0, -1.0);\n    result += mat4(0.040326044, 0.17290725, -0.1220239, -0.09594783, -0.025229257, 0.17913155, -0.26623353, -0.033396784, -0.03075146, 0.009143897, -0.0136083895, -0.13886899, 0.075683735, -0.11584183, 0.22182357, 0.19350322) * go_1(1.0, 0.0);\n    result += mat4(0.15726025, -0.10215694, -0.060057458, 0.26487043, -0.04075552, -0.016496127, 0.0015382086, 0.108562306, 0.026795091, 0.0441233, -0.08754318, -0.0460157, 0.048422016, 0.14107347, 0.07986661, 0.1047697) * go_1(1.0, 1.0);\n    result += vec4(0.0766796, 0.08115133, -0.05703058, 0.14025708);\n    return result;\n}\n//!DESC Anime4K-v3.2-Upscale-CNN-x2-(M)-Conv-4x3x3x8\n//!HOOK MAIN\n//!BIND conv2d_2_tf\n//!SAVE conv2d_3_tf\n//!WIDTH conv2d_2_tf.w\n//!HEIGHT conv2d_2_tf.h\n//!COMPONENTS 4\n//!WHEN OUTPUT.w MAIN.w / 1.200 > OUTPUT.h MAIN.h / 1.200 > *\n#define go_0(x_off, y_off) (max((conv2d_2_tf_texOff(vec2(x_off, y_off))), 0.0))\n#define go_1(x_off, y_off) (max(-(conv2d_2_tf_texOff(vec2(x_off, y_off))), 0.0))\nvec4 hook() {\n    vec4 result = mat4(-0.18038331, 0.21830973, -0.10019419, -0.022745568, -0.14944611, -0.15669158, 0.46361133, -0.07289843, 0.02976627, -0.09000817, 0.113060996, 0.05635241, 0.012762965, -0.022688959, 0.01629751, 0.061114635) * go_0(-1.0, -1.0);\n    result += mat4(0.024338024, -0.10004009, -0.13709056, -0.0851965, 0.23927099, -0.024349794, -0.16574804, 0.084686354, -0.047885604, 0.09688507, -0.12733915, 0.06980246, 0.11480734, 0.014669346, -0.07505829, 0.04676309) * go_0(-1.0, 0.0);\n    result += mat4(0.054203495, 0.011881634, -0.036115017, -0.0686298, -0.13682245, -0.15678032, 0.057050128, -0.03368558, 0.13011025, 0.033391044, -0.09841339, -0.027057761, -0.18701133, 0.20852546, -0.13660902, 0.0005817616) * go_0(-1.0, 1.0);\n    result += mat4(-0.08077834, 0.35952288, -0.07647382, -0.0033230998, 0.13929126, -0.09155619, 0.14128102, 0.16005981, 0.18161216, -0.09485738, 0.0029118075, 0.052682754, 0.03242074, 0.08299826, 0.073796146, -0.06446532) * go_0(0.0, -1.0);\n    result += mat4(-0.36655015, 0.4606936, 0.19073649, 0.31655258, -0.006838053, -0.579939, 0.089126326, -0.14021218, -0.3437716, 0.16714323, 0.17705944, -0.22418492, -0.3883696, -0.2302651, 0.2581861, 0.21983066) * go_0(0.0, 0.0);\n    result += mat4(0.0992383, -0.014257871, -0.023896435, 0.19868234, 0.0408007, 0.07995299, 0.16102871, -0.11668251, 0.22458278, -0.05587917, 0.19373615, -0.016202094, -0.25106144, 0.15634494, 0.11624891, -0.2930768) * go_0(0.0, 1.0);\n    result += mat4(0.024616942, 0.36248252, -0.14779098, -0.019894283, -0.007111256, 0.010641561, -0.09541178, 0.21236233, 0.009501827, 0.08132797, -0.13983901, 0.027207611, 0.038444366, -0.013995817, -0.16242191, 0.03294123) * go_0(1.0, -1.0);\n    result += mat4(0.0131698875, -0.18124102, -0.13503514, -0.06099072, 0.07422735, -0.20906176, -0.049005672, 0.08739405, -0.031758767, -0.1978915, 0.23094437, 0.54512614, 0.21338555, -0.011205669, -0.23727885, -0.29533875) * go_0(1.0, 0.0);\n    result += mat4(-0.0010255767, -0.07168225, -0.033568826, 0.22161655, -0.087293416, 0.11350447, 0.13653576, 0.061226424, -0.13074352, 0.058425818, 0.038460605, 0.2749964, -0.012814839, 0.085885845, -0.038151987, -0.17960808) * go_0(1.0, 1.0);\n    result += mat4(0.19728905, -0.040724937, -0.18270236, 0.046735186, 0.03507326, 0.119867206, -0.12691991, 0.18119748, -0.052895024, 0.11348764, -0.043787055, 0.004703516, 0.006752757, -0.06939761, -0.009801806, -0.075640485) * go_1(-1.0, -1.0);\n    result += mat4(0.051735226, 0.1732299, -0.10672899, 0.0320877, -0.4913656, 0.2102274, 0.43920282, 0.059108034, 0.08349019, -0.16517872, 0.15436842, -0.1075667, 0.022741623, -0.26693836, 0.3645307, 0.017874828) * go_1(-1.0, 0.0);\n    result += mat4(0.034464058, 0.014929155, 0.054227423, 0.14167373, -0.0023630706, -0.08904212, 0.11918041, -0.034539603, 0.06048089, -0.06807333, 0.14447778, 0.035260547, 0.09979546, -0.1924939, 0.14596114, -0.12069667) * go_1(-1.0, 1.0);\n    result += mat4(-0.04427228, -0.23673469, 0.010357103, -0.2907043, -0.06845721, -0.078984015, 0.06867713, -0.058163825, -0.12154615, 0.08430951, 0.1922373, 0.030108064, -0.43081748, -0.38715646, -0.022240646, -0.15403675) * go_1(0.0, -1.0);\n    result += mat4(0.46885306, -0.33421394, -0.6695223, -0.41841158, 0.30317923, 0.24244753, -0.1047785, -0.18656285, 0.06261881, -0.4405616, 0.24233986, 0.40070608, 0.81440526, 0.11305212, -0.8826317, -0.023478031) * go_1(0.0, 0.0);\n    result += mat4(-0.07879348, -0.024378026, -0.041883785, -0.17030984, 0.23229122, -0.011237109, 0.12058088, 0.20766267, -0.36519575, 0.09599417, -0.1271098, 0.06990154, 0.21161246, 0.041002538, -0.36046275, 0.007304667) * go_1(0.0, 1.0);\n    result += mat4(0.10873893, 0.003872542, -0.13476561, -0.036068805, -0.054637462, 0.02304618, 0.04707738, -0.2856381, 0.07124422, 0.010866545, 0.20484549, -0.008342406, -0.43660247, -0.041055538, 0.33536008, -0.060022205) * go_1(1.0, -1.0);\n    result += mat4(0.1966458, 0.0016302796, -0.25712642, -0.09639119, -0.006955351, 0.10882133, 0.1107341, 0.062697805, -0.1074494, 0.17361663, 0.6429869, -0.39846307, -0.26302996, 0.048710946, 0.40387508, 0.4299715) * go_1(1.0, 0.0);\n    result += mat4(0.18948616, 0.24086732, -0.064474985, -0.11069709, 0.1279659, -0.13438123, -0.028438117, 0.125883, 0.018153818, -0.21942288, 0.020390838, -0.22797634, -0.10821287, -0.17175092, 0.122016855, 0.20699544) * go_1(1.0, 1.0);\n    result += vec4(-0.05101961, -0.060740646, -0.024465766, 0.058471628);\n    return result;\n}\n//!DESC Anime4K-v3.2-Upscale-CNN-x2-(M)-Conv-4x3x3x8\n//!HOOK MAIN\n//!BIND conv2d_3_tf\n//!SAVE conv2d_4_tf\n//!WIDTH conv2d_3_tf.w\n//!HEIGHT conv2d_3_tf.h\n//!COMPONENTS 4\n//!WHEN OUTPUT.w MAIN.w / 1.200 > OUTPUT.h MAIN.h / 1.200 > *\n#define go_0(x_off, y_off) (max((conv2d_3_tf_texOff(vec2(x_off, y_off))), 0.0))\n#define go_1(x_off, y_off) (max(-(conv2d_3_tf_texOff(vec2(x_off, y_off))), 0.0))\nvec4 hook() {\n    vec4 result = mat4(-0.14533128, 0.07266841, 0.13238011, -0.23328504, 0.031516243, 0.058471266, -0.06394412, 0.090752736, -0.0042359144, 0.12357294, -0.04377495, 0.0011743477, 0.05412243, -0.08146249, 0.04002749, -0.032876283) * go_0(-1.0, -1.0);\n    result += mat4(-0.036972385, -0.15238069, -0.3453321, -0.36025128, 0.07597202, -0.02368151, -0.3889606, 0.34607083, 0.3133179, -0.21712309, -0.4210954, 0.21450534, 0.15226828, 0.25326282, 0.45327064, -0.3350824) * go_0(-1.0, 0.0);\n    result += mat4(0.019018406, -0.33060563, -0.092601225, 0.14970545, 0.1441509, -0.19228427, -0.032771986, 0.26331595, 0.052981265, -0.06627376, -0.08634131, 0.038706224, 0.13403937, -4.4842476e-05, 0.049002815, -0.12719193) * go_0(-1.0, 1.0);\n    result += mat4(0.17527401, -0.0035254909, -0.047959115, -0.4526988, -0.07510284, 0.0013256798, -0.07539148, 0.24220634, -0.08708839, -0.14494033, -0.17085724, -0.099797316, 0.0068515535, -0.08918779, 0.27164719, -0.1702649) * go_0(0.0, -1.0);\n    result += mat4(0.31848368, 0.48983255, -0.44140294, -0.65174145, -0.004199057, 0.19494705, 0.5196497, -0.027118586, 0.032509074, -0.23900363, -0.14489244, 0.36314297, -0.23168536, -0.20960593, 0.61471456, 0.12401275) * go_0(0.0, 0.0);\n    result += mat4(-0.24317405, 0.21560913, 0.15564032, 0.11606844, -0.15039803, -0.59578896, 0.14100945, -0.026194477, 0.37237462, -0.49472088, -0.15215331, -0.38820064, -0.25089455, -0.29643852, -0.09513793, 0.019779462) * go_0(0.0, 1.0);\n    result += mat4(0.12498539, 0.0710632, -0.25012368, -0.2272255, -0.08647026, 0.12277892, 0.011025097, -0.12168395, -0.13489573, 0.016708186, -0.15583871, -0.057124946, 0.1216943, 0.019803725, 0.06952334, -0.032985855) * go_0(1.0, -1.0);\n    result += mat4(0.28794885, 0.33783793, -0.14469545, -0.081780486, -0.50320613, -0.067601606, -0.06847453, -0.021648854, -0.34295765, 0.15071863, -0.06619896, -0.084465064, 0.31909832, 0.015414661, 0.14930317, -0.11295768) * go_0(1.0, 0.0);\n    result += mat4(0.24530606, 0.25526014, 0.09971985, -0.07749641, -0.2361951, -0.07997673, 0.03617294, 0.02959561, -0.4498983, -0.014073485, -0.20587012, 0.06396779, 0.1262825, 0.027433183, 0.14469334, 0.011538011) * go_0(1.0, 1.0);\n    result += mat4(-0.038572453, -0.023108613, -0.039481267, -0.012160024, -0.004521989, -0.028665857, 0.04295255, 0.10580258, 0.05439479, -0.072261885, 0.11030243, 0.08934696, 0.09133867, 0.017547369, 0.097613186, 0.05491059) * go_1(-1.0, -1.0);\n    result += mat4(-0.09972817, 0.057730395, 0.12665828, 0.32861367, -0.16186063, 0.0745509, 0.2394045, -0.08687853, -0.034404907, -0.05843572, 0.0684561, -0.1355754, 0.19248672, -0.60372186, 0.12583947, 0.4388962) * go_1(-1.0, 0.0);\n    result += mat4(0.10341107, 0.061113223, 0.08773817, -0.082504354, -0.16612078, 0.2681751, 0.019737698, -0.17122322, -0.135949, 0.3048101, 0.087803006, 0.11373851, 0.013192192, -0.27022064, 0.35529897, -0.15321451) * go_1(-1.0, 1.0);\n    result += mat4(-0.032835662, 0.11123062, -0.11322452, -0.17300649, 0.04680824, 0.12849288, 0.17269878, -0.048671383, 0.05189037, -0.009078046, 0.22105052, 0.013008137, -0.009738674, 0.15391739, 0.20969556, 0.14189166) * go_1(0.0, -1.0);\n    result += mat4(-0.47377753, 0.3038031, 0.18604809, 0.1931698, -0.2964668, -0.12287907, -0.7107761, 0.26619422, -0.33923018, 0.19200724, 0.013786281, -0.17496964, 0.079325035, -0.3694445, 0.0054486147, -0.33018264) * go_1(0.0, 0.0);\n    result += mat4(0.14903802, -0.028043179, 1.5238678e-05, 0.021232028, 0.16025065, 0.14746875, -0.22831628, -0.12177345, 0.038778774, 0.32188168, -0.042017702, 0.27155936, 0.17920609, 0.04099755, 0.28527525, 0.074623376) * go_1(0.0, 1.0);\n    result += mat4(0.057019282, -0.112741895, 0.030361209, 0.14567861, 0.056265317, -0.01573537, -0.06707608, 0.016657263, 0.09829025, -0.026795063, 0.023042196, 0.09438241, -0.025483066, -0.052787006, 0.19730279, 0.021218104) * go_1(1.0, -1.0);\n    result += mat4(0.19868211, -0.01531125, 0.108596824, -0.035456363, 0.0033609823, 0.057961613, -0.013726211, 0.101742364, 0.33357215, 0.14468077, 0.29711527, -0.24662566, -0.119014986, -0.1899639, 0.11246697, -0.0035374009) * go_1(1.0, 0.0);\n    result += mat4(-0.05602109, -0.15539522, 0.010730943, 0.057116497, -0.02037749, 0.084210664, -0.028235348, 0.10574697, 0.056925274, 0.07922333, -0.090088, 0.1615985, -0.0044301567, -0.089945644, 0.024176618, -0.041844133) * go_1(1.0, 1.0);\n    result += vec4(0.0015292584, -0.043625206, -0.09429898, -0.06280405);\n    return result;\n}\n//!DESC Anime4K-v3.2-Upscale-CNN-x2-(M)-Conv-4x3x3x8\n//!HOOK MAIN\n//!BIND conv2d_4_tf\n//!SAVE conv2d_5_tf\n//!WIDTH conv2d_4_tf.w\n//!HEIGHT conv2d_4_tf.h\n//!COMPONENTS 4\n//!WHEN OUTPUT.w MAIN.w / 1.200 > OUTPUT.h MAIN.h / 1.200 > *\n#define go_0(x_off, y_off) (max((conv2d_4_tf_texOff(vec2(x_off, y_off))), 0.0))\n#define go_1(x_off, y_off) (max(-(conv2d_4_tf_texOff(vec2(x_off, y_off))), 0.0))\nvec4 hook() {\n    vec4 result = mat4(0.06051604, -0.028152643, -0.21418124, 0.13032125, 0.42565975, -0.09571944, -0.34494513, 0.30004, -0.073245734, -0.028659137, 0.0032105136, -0.05009555, -0.048971225, 0.04814533, 0.002843805, -0.046224426) * go_0(-1.0, -1.0);\n    result += mat4(-0.07495975, 0.018714864, 0.21229684, -0.13614887, 0.79988647, -0.0697328, 0.38232988, 0.24165109, 0.25947478, -0.0009418982, -0.17369923, 0.10007766, 0.024117598, 0.028611807, 0.15090801, -0.06344829) * go_0(-1.0, 0.0);\n    result += mat4(-0.07982219, 0.0900347, 0.007609254, -0.0034791247, 0.013611781, -0.13560618, 0.09685799, 0.06276075, 0.134693, -0.14370437, -0.25175703, -0.0016138123, -0.0075672898, -0.13325731, -0.061100446, 0.0059743375) * go_0(-1.0, 1.0);\n    result += mat4(-0.039018434, -0.19668463, -0.43018532, 0.31886247, 0.4965479, 0.114569925, 0.19110382, 0.27343535, 0.0707728, -0.11877004, -0.25827697, 0.37012872, 0.1474777, 0.07056952, -0.14965728, 0.061595406) * go_0(0.0, -1.0);\n    result += mat4(0.506543, -0.16268773, 0.455319, -0.0702646, 0.70102173, -0.14041683, 0.70184857, 0.4817842, -0.3389246, -0.14463086, 0.13763213, -1.1259074, 0.47722015, 0.38352612, -0.04293366, -0.5604627) * go_0(0.0, 0.0);\n    result += mat4(0.17606944, 0.15897374, 0.13499324, 0.29241478, -0.032824475, 0.11128662, -0.22204424, -0.051803727, 0.013195331, -0.42040786, -0.3950585, 0.70745844, 0.38646924, -0.19080774, -0.15171832, -0.10742828) * go_0(0.0, 1.0);\n    result += mat4(-0.039278325, 0.18421806, -0.044948544, 0.07902063, -0.2149251, 0.09913459, -0.09743655, -0.26899317, -0.002695496, -0.07554527, -0.22373366, 0.17830558, -0.047994815, -0.06789183, -0.06755918, -0.104452066) * go_0(1.0, -1.0);\n    result += mat4(-0.0493473, -0.30411786, -0.056439694, -0.06582185, -0.21309847, 0.100670904, -0.22966193, -0.045954112, 0.12728062, -0.25081897, -0.094699375, -0.4036555, 0.060854495, -0.64373237, -0.21522263, -0.6683476) * go_0(1.0, 0.0);\n    result += mat4(0.063481025, 0.11744312, -0.043330096, 0.33817932, -0.06679828, -0.23207302, -0.10188898, -0.10590511, 0.058780864, 0.047292337, -0.11834696, 0.10076128, -0.036641665, 0.30200714, -0.0002892557, -0.10303763) * go_0(1.0, 1.0);\n    result += mat4(-0.10842604, 0.042055763, 0.29702973, -0.07409644, -0.030164458, -0.012098744, -0.06396587, -0.08787527, 0.051854923, 0.12997511, 0.11468497, 0.15022379, 0.007814715, 0.014517445, 0.025484756, 0.01078619) * go_1(-1.0, -1.0);\n    result += mat4(-0.29229385, 0.040265664, -0.15376821, 0.075579196, -0.05593569, -0.045405343, 0.12099204, 0.1571252, 0.17841713, 0.04673325, 0.14550509, 0.08603346, -0.049786013, 0.06121843, -0.16273825, -0.13857752) * go_1(-1.0, 0.0);\n    result += mat4(0.06903744, 0.2628764, -0.13582836, -0.35678583, -0.13821034, -0.019381443, -0.19570538, -0.09298511, 0.08965436, 0.09745909, 0.20055099, 0.024967568, 0.08144204, 0.004633625, 0.12809834, -0.009431525) * go_1(-1.0, 1.0);\n    result += mat4(0.09784006, 0.010729353, 0.046643205, -0.110926524, -0.21556224, 0.00016300633, 0.122175336, 0.15004392, 0.013864355, 0.24767809, 0.13865592, 0.0155424485, -0.1450483, -0.15688781, -0.06195043, -0.13745981) * go_1(0.0, -1.0);\n    result += mat4(0.018991318, 0.55401963, 0.11709872, -0.028442185, -0.46035343, -0.10215539, -0.60193926, 0.47882316, -0.23346989, 0.037200127, 0.22814943, -0.08231696, -0.36430013, -0.011152757, 0.48752213, 0.29796222) * go_1(0.0, 0.0);\n    result += mat4(-0.07258066, -0.023222538, 0.23230423, -0.30317304, 0.03942911, -0.06899803, 0.23778579, 0.07418621, -0.17443737, 0.33387753, 0.007354842, -0.123447575, -0.1745315, 0.11071779, -0.11949625, -0.22832453) * go_1(0.0, 1.0);\n    result += mat4(-0.024909232, -0.0308135, 0.12170621, -0.13298757, 0.045828197, -0.1532345, -0.06633672, 0.23591088, 0.04964077, 0.14091493, 0.038343724, -0.029780807, 0.05762822, -0.048930667, -0.02434709, 0.07109019) * go_1(1.0, -1.0);\n    result += mat4(-0.16039175, 0.3004474, -0.17278233, 0.13677922, 0.18838613, 0.15054552, 0.32901475, -0.1288333, 0.26378244, -0.05119892, 0.34533516, 0.25180495, 0.19452183, 0.0843233, -0.08029368, 0.39877903) * go_1(1.0, 0.0);\n    result += mat4(-0.07097129, -0.26492423, -0.055032317, -0.093516104, -0.11795062, 0.04086253, -0.07989471, 0.059686553, 0.09378249, 0.45851848, 0.2510942, 0.19599153, 0.019765077, -0.02920918, -0.04125142, -0.13859107) * go_1(1.0, 1.0);\n    result += vec4(0.04400571, -0.04015565, 0.0140529545, 0.05474095);\n    return result;\n}\n//!DESC Anime4K-v3.2-Upscale-CNN-x2-(M)-Conv-4x3x3x8\n//!HOOK MAIN\n//!BIND conv2d_5_tf\n//!SAVE conv2d_6_tf\n//!WIDTH conv2d_5_tf.w\n//!HEIGHT conv2d_5_tf.h\n//!COMPONENTS 4\n//!WHEN OUTPUT.w MAIN.w / 1.200 > OUTPUT.h MAIN.h / 1.200 > *\n#define go_0(x_off, y_off) (max((conv2d_5_tf_texOff(vec2(x_off, y_off))), 0.0))\n#define go_1(x_off, y_off) (max(-(conv2d_5_tf_texOff(vec2(x_off, y_off))), 0.0))\nvec4 hook() {\n    vec4 result = mat4(-0.014236042, -0.0031431736, -0.1551387, 0.12515116, -0.28528872, 0.36161992, 0.15750743, -0.17111474, 0.13792591, -0.0657419, -0.17471549, 0.14650472, 0.034169197, -0.019157575, 0.23520657, -0.20358163) * go_0(-1.0, -1.0);\n    result += mat4(0.02015035, 0.12993371, 0.11199667, -0.09854378, 0.5001741, 0.03462961, 0.24919736, 0.08505297, -0.20902094, -0.24141377, -0.15360375, 0.049974803, -0.037157424, -0.048510186, 0.20106035, -0.118480384) * go_0(-1.0, 0.0);\n    result += mat4(0.086798504, -0.009607818, 0.034812123, -0.005187592, 0.0351509, 0.021755, -0.04996161, -0.041231696, 0.0020545553, 0.015730752, -0.07507172, 0.018597523, -0.02393343, 0.07624775, 0.03892451, -0.0025574185) * go_0(-1.0, 1.0);\n    result += mat4(0.035725456, 0.06809103, 0.51926994, -0.39983147, -0.16402833, -0.1243394, -0.25922915, 0.28285915, 0.15959994, -0.2351732, 0.2650535, -0.30193794, -0.11468332, 0.050777763, -0.51894253, 0.4408367) * go_0(0.0, -1.0);\n    result += mat4(-0.27042082, 0.22243942, 0.14902467, 0.38428563, 0.46612173, 0.5169912, -0.22330502, -0.11300288, -0.36141354, 0.0668681, 0.2984152, 0.1275798, -0.24121419, 0.2952039, -0.45109174, -0.3822957) * go_0(0.0, 0.0);\n    result += mat4(0.26543504, -0.05742226, -0.052103903, -0.013124308, -0.14358385, -0.04024543, 0.07665455, -0.012301872, -0.18752757, -0.03913891, 0.038205814, -0.006583095, -0.25550908, -0.25725332, -0.12454206, -0.0058936924) * go_0(0.0, 1.0);\n    result += mat4(-0.0018946569, 0.019746022, -0.13080788, 0.11450627, -0.013743845, -0.027179785, -0.14425103, 0.07109661, 0.023703793, 0.086905524, 0.03151253, 0.0132474145, 0.041018624, 0.04548913, 0.2718715, -0.20008296) * go_0(1.0, -1.0);\n    result += mat4(-0.076830454, 0.11652955, 0.5068201, -0.3082819, 0.058615055, -0.006765798, -0.057522714, 0.049981344, -0.006897243, -0.21763432, 0.16896053, -0.21176189, -0.061227098, 0.03566485, 0.08901554, -0.050980624) * go_0(1.0, 0.0);\n    result += mat4(0.02327798, 0.07662976, 0.034811985, -0.03238033, -0.0021881019, -0.030997375, -0.069672935, 0.04040273, -0.1217442, 0.104173124, 0.09862539, 0.020557549, -0.022286594, 0.10287763, -0.021694934, 0.07542515) * go_0(1.0, 1.0);\n    result += mat4(0.124069154, -0.08579466, -0.07816314, 0.11332851, -0.034682628, -0.11038275, 0.04750615, -0.096100725, 0.039588403, -0.15149672, -0.05529172, 0.034304325, -0.022520235, -0.05023852, -0.2674731, 0.21886522) * go_1(-1.0, -1.0);\n    result += mat4(-0.1948599, -0.14946899, -0.39548838, 0.18042913, -0.007919619, 0.19826505, 0.23789087, 0.009140256, 0.11857748, 0.18215668, 0.13606293, -0.09209675, -0.080678545, -0.020431137, -0.07728839, -0.051353537) * go_1(-1.0, 0.0);\n    result += mat4(-0.07616472, -0.0032800382, -0.045657665, -0.039144326, -0.37786487, -0.08877774, 0.053579114, -0.070886396, 0.011311804, 0.107276045, 0.013236154, 0.009832061, 0.08292063, 0.12258811, 0.0005569043, -0.009806432) * go_1(-1.0, 1.0);\n    result += mat4(-0.28062925, 0.15946878, -0.1021801, -0.06471589, -0.26999477, 0.21230288, -0.14243907, 0.2555922, -0.09608517, 0.26339412, 0.20891234, -0.23538485, 0.33958244, -0.12569186, 0.43289876, -0.33462036) * go_1(0.0, -1.0);\n    result += mat4(0.16265294, 0.2625464, -0.34452894, 0.2233622, 0.13850005, -0.42999864, -0.5385177, -0.11035979, 0.51662, -0.78238726, -0.09422375, 0.83759475, 0.44468537, 0.14301361, 0.108906105, 1.1596143) * go_1(0.0, 0.0);\n    result += mat4(-0.73757625, -0.12369605, 0.23523071, 0.006587637, -0.15445381, 0.22757277, 0.052819528, 0.10183905, -0.07912228, -0.16998893, -0.13360223, 0.014348178, -0.17778571, -0.41047302, 0.10241381, -0.08526306) * go_1(0.0, 1.0);\n    result += mat4(0.14712952, 0.048995696, 0.05299946, -0.06817572, 0.1498064, -0.079825334, 0.40354064, -0.31789717, -0.1998377, 0.00955295, -0.32318407, 0.30898204, -0.039571725, -0.026203401, -0.16292085, 0.08574385) * go_1(1.0, -1.0);\n    result += mat4(-0.6353329, -0.56000775, -0.17279743, 0.18198174, -0.19555812, 0.056538377, 0.34365895, -0.07799055, 0.19011354, -0.13952748, 0.029196098, -0.19596763, -0.069196045, -0.17402656, 0.07948411, -0.016226962) * go_1(1.0, 0.0);\n    result += mat4(0.25592864, 0.083498634, -0.28515807, 0.10789751, 0.0043962947, 0.07085363, 0.048724182, -0.025131436, -0.0049440865, -0.033094388, -0.032935806, 0.04266025, 0.20026933, 0.0927841, -0.006839351, -0.013012285) * go_1(1.0, 1.0);\n    result += vec4(0.02021373, 0.0014037411, 0.0012718709, 0.017278494);\n    return result;\n}\n//!DESC Anime4K-v3.2-Upscale-CNN-x2-(M)-Conv-4x1x1x56\n//!HOOK MAIN\n//!BIND conv2d_tf\n//!BIND conv2d_1_tf\n//!BIND conv2d_2_tf\n//!BIND conv2d_3_tf\n//!BIND conv2d_4_tf\n//!BIND conv2d_5_tf\n//!BIND conv2d_6_tf\n//!SAVE conv2d_last_tf\n//!WIDTH conv2d_tf.w\n//!HEIGHT conv2d_tf.h\n//!COMPONENTS 4\n//!WHEN OUTPUT.w MAIN.w / 1.200 > OUTPUT.h MAIN.h / 1.200 > *\n#define g_0 (max((conv2d_tf_tex(conv2d_tf_pos)), 0.0))\n#define g_1 (max(-(conv2d_tf_tex(conv2d_tf_pos)), 0.0))\n#define g_2 (max((conv2d_1_tf_tex(conv2d_1_tf_pos)), 0.0))\n#define g_3 (max(-(conv2d_1_tf_tex(conv2d_1_tf_pos)), 0.0))\n#define g_4 (max((conv2d_2_tf_tex(conv2d_2_tf_pos)), 0.0))\n#define g_5 (max(-(conv2d_2_tf_tex(conv2d_2_tf_pos)), 0.0))\n#define g_6 (max((conv2d_3_tf_tex(conv2d_3_tf_pos)), 0.0))\n#define g_7 (max(-(conv2d_3_tf_tex(conv2d_3_tf_pos)), 0.0))\n#define g_8 (max((conv2d_4_tf_tex(conv2d_4_tf_pos)), 0.0))\n#define g_9 (max(-(conv2d_4_tf_tex(conv2d_4_tf_pos)), 0.0))\n#define g_10 (max((conv2d_5_tf_tex(conv2d_5_tf_pos)), 0.0))\n#define g_11 (max(-(conv2d_5_tf_tex(conv2d_5_tf_pos)), 0.0))\n#define g_12 (max((conv2d_6_tf_tex(conv2d_6_tf_pos)), 0.0))\n#define g_13 (max(-(conv2d_6_tf_tex(conv2d_6_tf_pos)), 0.0))\nvec4 hook() {\n    vec4 result = mat4(-0.0067711817, 0.08160003, 0.0247279, 0.03084815, -0.026977416, -0.02120602, -0.025078611, -0.029852165, -0.011627478, -0.012742972, 0.022736797, -0.0028815821, -0.007515677, 0.0172887, -0.023259213, 0.009608947) * g_0;\n    result += mat4(-0.028660107, -0.014015208, -0.027838672, -0.013171922, 0.0029435428, 0.027047642, -0.017478354, 0.022834882, -0.037572853, -0.0034044068, -0.0149029335, -0.013362301, 0.009827443, -0.015742151, -0.0074795415, -0.0022266617) * g_1;\n    result += mat4(-0.07579662, -0.039754186, -0.066026606, -0.046816852, 0.1099032, 0.043956704, 0.073109835, 0.04680284, -0.06896613, -0.008838632, -0.044584926, -0.01319039, -0.0021152915, -0.04503326, 0.027061926, -0.028334105) * g_2;\n    result += mat4(0.15458213, 0.059769996, 0.09327123, -0.028782733, 0.023459995, -0.15390377, -0.13432898, -0.1127775, 0.072764635, -0.0020463336, 0.034736466, -0.0012086042, -0.05847183, -0.029952323, 0.052969377, 0.09590908) * g_3;\n    result += mat4(-0.07476772, -0.016574614, 0.04131183, 0.017335678, 0.009654406, 0.072183535, -0.002266456, 0.086873695, 9.310129e-05, 0.0056416965, -0.004188391, 0.023132093, -0.05183336, -0.025825873, -0.03684392, -0.0075729224) * g_4;\n    result += mat4(0.00878842, 0.03869637, -0.035759524, 0.003345386, -0.064184256, -0.034568302, -0.06672922, -0.0686381, -0.06794392, -0.10685906, 0.04679947, -0.012535639, 0.006932529, -0.007783515, 0.109123886, 0.13804391) * g_5;\n    result += mat4(-0.03160699, 0.050473, -0.09030729, 0.0649397, 0.11466501, 0.17912874, -0.0081851315, 0.052244574, 0.051632743, 0.061941486, 0.06546816, 0.12174249, -0.05104755, -0.018193979, -0.032196652, -0.035292786) * g_6;\n    result += mat4(0.013612735, -0.0024100312, -0.068611205, -0.07369285, -0.019647537, -0.066944756, -0.010012875, -0.06785739, -0.062246565, -0.087313406, -0.044278186, -0.09368995, 0.052555013, 0.13604961, 0.05645059, 0.08763303) * g_7;\n    result += mat4(0.04218486, -0.05028401, 0.059086576, -0.03545452, 0.027737848, 0.0043074046, 0.0011001764, -0.073026665, -0.04094988, 0.044061556, -0.009812515, 0.06841999, -0.06612581, 0.037223976, -0.07759491, -0.04356598) * g_8;\n    result += mat4(-0.027558247, 0.014248466, -0.019813016, -0.058107473, -0.016717663, -0.020424338, 0.0053625097, -0.009917319, 0.013678771, 0.0113340765, 0.0061787106, -0.036083996, -0.020179711, -0.011310535, 0.054827053, -0.0008278952) * g_9;\n    result += mat4(0.028690035, -0.012079616, 0.11931408, -0.048533775, 0.069336995, 0.0049852817, 0.013774468, 0.035233382, -0.07384821, 0.0003354423, -0.0059171803, -0.04503906, 0.08727279, 0.005138857, -0.17724465, 0.055782065) * g_10;\n    result += mat4(-0.20744391, 0.24348328, -0.3145766, 0.17026486, -0.022870807, -0.01648648, -0.05912279, -0.012555373, -0.066004686, 0.03182394, 0.16285324, -0.1221846, -0.31816196, 0.007928748, 0.43180224, -0.015949022) * g_11;\n    result += mat4(0.16363169, 0.14781676, -0.2377973, -0.1571377, -0.09038187, 0.0046504294, 0.033955004, -0.051421452, 0.046735536, 0.006827522, -0.121338, 0.12671822, 0.15833299, -0.1858712, -0.1942371, 0.17336044) * g_12;\n    result += mat4(-0.018145572, -0.015550516, 0.044410378, 0.046016492, 0.084021375, 0.05327457, -0.008270992, -0.045435544, 0.07185879, -0.131923, 0.26721445, -0.26745328, -0.07093472, 0.042701527, 0.13793674, -0.095621444) * g_13;\n    result += vec4(0.016836504, 0.010161949, 0.021351453, 0.01278978);\n    return result;\n}\n//!DESC Anime4K-v3.2-Upscale-CNN-x2-(M)-Depth-to-Space\n//!HOOK MAIN\n//!BIND MAIN\n//!BIND conv2d_last_tf\n//!SAVE MAIN\n//!WIDTH conv2d_last_tf.w 2 *\n//!HEIGHT conv2d_last_tf.h 2 *\n//!WHEN OUTPUT.w MAIN.w / 1.200 > OUTPUT.h MAIN.h / 1.200 > *\nvec4 hook() {\n    vec2 f0 = fract(conv2d_last_tf_pos * conv2d_last_tf_size);\n    ivec2 i0 = ivec2(f0 * vec2(2.0));\n    float c0 = conv2d_last_tf_tex((vec2(0.5) - f0) * conv2d_last_tf_pt + conv2d_last_tf_pos)[i0.y * 2 + i0.x];\n    float c1 = c0;\n    float c2 = c1;\n    float c3 = c2;\n    return vec4(c0, c1, c2, c3) + MAIN_tex(MAIN_pos);\n}\n"
  },
  {
    "path": "app/src/main/assets/shaders/Anime4K_Upscale_CNN_x2_S.glsl",
    "content": "// MIT License\n\n// Copyright (c) 2019-2021 bloc97\n// All rights reserved.\n\n// Permission is hereby granted, free of charge, to any person obtaining a copy\n// of this software and associated documentation files (the \"Software\"), to deal\n// in the Software without restriction, including without limitation the rights\n// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell\n// copies of the Software, and to permit persons to whom the Software is\n// furnished to do so, subject to the following conditions:\n\n// The above copyright notice and this permission notice shall be included in all\n// copies or substantial portions of the Software.\n\n// THE SOFTWARE IS PROVIDED \"AS IS\", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR\n// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,\n// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE\n// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER\n// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,\n// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE\n// SOFTWARE.\n\n//!DESC Anime4K-v3.2-Upscale-CNN-x2-(S)-Conv-4x3x3x3\n//!HOOK MAIN\n//!BIND MAIN\n//!SAVE conv2d_tf\n//!WIDTH MAIN.w\n//!HEIGHT MAIN.h\n//!COMPONENTS 4\n//!WHEN OUTPUT.w MAIN.w / 1.200 > OUTPUT.h MAIN.h / 1.200 > *\n#define go_0(x_off, y_off) (MAIN_texOff(vec2(x_off, y_off)))\nvec4 hook() {\n    vec4 result = mat4(-0.0057322932, 0.12928207, -0.056848746, 0.18680117, -0.0306273, 0.25602463, 0.053723164, 0.20419341, 0.0018709862, 0.022848232, -0.04105527, 0.10169034, 0.0, 0.0, 0.0, 0.0) * go_0(-1.0, -1.0);\n    result += mat4(0.009471417, -0.12957802, 0.096014425, 0.21836184, 0.00021601951, -0.22997683, 0.23666254, 0.41192335, 0.021762101, 0.0047863554, 0.008233427, 0.108514786, 0.0, 0.0, 0.0, 0.0) * go_0(-1.0, 0.0);\n    result += mat4(-0.01156376, -0.18988979, 0.04614705, -0.044767227, 0.01050636, -0.26426336, 0.23741047, 0.0027636609, -0.027718676, -0.14202335, -0.016650287, -0.06637125, 0.0, 0.0, 0.0, 0.0) * go_0(-1.0, 1.0);\n    result += mat4(0.057809234, -0.11033858, 0.056533534, -0.06292466, 0.13880666, -0.18710336, 0.2441031, -0.25326246, 0.0032683122, -0.026437074, 0.0023248852, 7.640766e-05, 0.0, 0.0, 0.0, 0.0) * go_0(0.0, -1.0);\n    result += mat4(-0.49110603, 0.4429004, -0.44015464, -0.41174838, -0.87738293, 0.7808468, -1.0929365, -0.59699076, -0.18409836, 0.185138, -0.11773224, -0.17097276, 0.0, 0.0, 0.0, 0.0) * go_0(0.0, 0.0);\n    result += mat4(0.10580959, -0.055947904, -0.03431237, -0.080236495, 0.14862584, -0.15393938, -0.18872876, -0.3170681, 0.03559387, -0.003990826, 0.021298569, 0.012844483, 0.0, 0.0, 0.0, 0.0) * go_0(0.0, 1.0);\n    result += mat4(-0.040715586, -0.25781113, 0.08896714, -0.1225879, -0.15790503, -0.54010904, 0.29588607, 0.10401059, 0.003413123, -0.108357325, 0.0112870345, -0.11888622, 0.0, 0.0, 0.0, 0.0) * go_0(1.0, -1.0);\n    result += mat4(0.0049315444, 0.02376202, -0.08224771, 0.121118225, -0.041512914, -0.027994309, -0.585988, -0.069672115, -0.017247835, 0.0056576864, 0.04319012, 0.055003505, 0.0, 0.0, 0.0, 0.0) * go_0(1.0, 0.0);\n    result += mat4(0.37521392, 0.15916082, 0.059708964, 0.19046007, 0.8120325, 0.38343868, 0.3436578, 0.5287958, 0.16570656, 0.06957687, 0.014022592, 0.074799836, 0.0, 0.0, 0.0, 0.0) * go_0(1.0, 1.0);\n    result += vec4(-0.01050964, -0.00939481, 0.17684458, 0.027366742);\n    return result;\n}\n//!DESC Anime4K-v3.2-Upscale-CNN-x2-(S)-Conv-4x3x3x8\n//!HOOK MAIN\n//!BIND conv2d_tf\n//!SAVE conv2d_1_tf\n//!WIDTH conv2d_tf.w\n//!HEIGHT conv2d_tf.h\n//!COMPONENTS 4\n//!WHEN OUTPUT.w MAIN.w / 1.200 > OUTPUT.h MAIN.h / 1.200 > *\n#define go_0(x_off, y_off) (max((conv2d_tf_texOff(vec2(x_off, y_off))), 0.0))\n#define go_1(x_off, y_off) (max(-(conv2d_tf_texOff(vec2(x_off, y_off))), 0.0))\nvec4 hook() {\n    vec4 result = mat4(-0.011029496, 0.05866063, -0.09460646, -0.017664742, -0.022488879, 0.18384217, -0.00397663, -0.064733066, 0.08466802, 0.10667488, 8.0212536e-05, 0.0908869, 0.13580276, 0.00097438256, 0.12176522, -0.08218466) * go_0(-1.0, -1.0);\n    result += mat4(0.16062798, -0.10190268, 0.03280682, 0.05621916, -0.009684231, -0.08464307, 0.17058301, -0.096469186, 0.1967505, -0.1450099, 0.093607284, -0.28240147, -0.21377413, 0.10079291, -0.1741522, 0.17330575) * go_0(-1.0, 0.0);\n    result += mat4(-0.060160473, 0.06316997, 0.0046929033, -0.049405966, 0.13851729, 0.06830702, -0.0586872, -0.040827133, 0.007052838, -0.03576886, -0.111261636, 0.039155316, -0.07380389, -0.09369825, 0.04471156, 0.09678487) * go_0(-1.0, 1.0);\n    result += mat4(-0.36683616, -0.035950605, -0.24414362, -0.009159744, 0.19335322, -0.099253505, 0.075083904, -0.00076695543, 0.65291303, -0.25599423, 0.19827642, 0.065899536, -0.07423247, -0.068967685, 0.0050554527, -0.060272824) * go_0(0.0, -1.0);\n    result += mat4(-0.020688485, -0.83178276, 0.11104878, 0.26454413, 0.13655476, 0.37675047, -0.22219229, -0.01751935, 0.44552696, 0.92510307, 0.16063261, -0.62011045, 0.19366647, -0.06996067, -0.2504841, 0.00803723) * go_0(0.0, 0.0);\n    result += mat4(0.0051537007, -0.057168536, -0.16110587, 0.25232598, -0.04447099, 0.11997351, 0.14808103, -0.34443566, -0.26212573, -0.21970181, 0.2724405, 0.21050811, -0.07949061, -0.064808235, -0.21208277, -0.0042361654) * go_0(0.0, 1.0);\n    result += mat4(-0.0888952, -0.20169449, 0.19144905, -0.016882861, -0.013283103, 0.07552998, -0.24686803, 0.012453213, -0.065454446, -0.016123284, -0.47316182, 0.070926026, 0.09219782, 0.13118166, 0.074736096, 0.0077910526) * go_0(1.0, -1.0);\n    result += mat4(0.5832154, 0.1138069, -0.039765622, 0.3182784, -0.25497997, 0.0013993139, 0.39285088, -0.48511526, -0.39891505, -0.19094779, -0.082146175, -0.20826934, 0.020590555, -0.0012490178, -0.4398621, 0.14377014) * go_0(1.0, 0.0);\n    result += mat4(0.21917395, 3.4314657e-05, 0.25734863, -0.3433305, 0.015720673, 0.2676127, -0.06807297, 0.15040149, -0.23638041, -0.0050233034, -0.13666134, 0.4542111, -0.033572577, -0.08450588, -0.23341487, 0.053490847) * go_0(1.0, 1.0);\n    result += mat4(-0.17482175, 0.057647135, 0.33135444, 0.0850751, -0.1718849, -0.0854123, 0.036795795, -0.13874969, -0.10903869, -0.19007301, -0.06064334, -0.03786032, -0.036696054, 0.07844446, 0.012523185, -0.01562906) * go_1(-1.0, -1.0);\n    result += mat4(-0.04411997, -0.10331819, 0.10050193, 0.12406485, 0.07431592, 0.30109692, -0.17511666, -0.13263564, -0.10192587, 0.07821255, -0.22415096, 0.25552443, 0.17881326, -0.13914281, 0.109979235, -0.0016463579) * go_1(-1.0, 0.0);\n    result += mat4(-0.01911644, -0.15412527, 0.028903123, 0.20831817, 0.00375175, 0.08110953, 0.074919395, -0.17581624, -0.015677985, 0.06504228, 0.08817818, -0.12518327, -0.09537373, 0.028905088, -0.051288474, 0.054334078) * go_1(-1.0, 1.0);\n    result += mat4(0.2852779, -0.28924024, 0.36805123, 0.21079305, -0.28336474, 0.1679663, -0.08641141, -0.10699407, -0.16090055, 0.1287612, -0.15910125, 0.05734755, 0.15883245, 0.0053026294, 0.080674745, 0.0505137) * go_1(0.0, -1.0);\n    result += mat4(0.17639062, 0.3790122, -0.19588692, -0.020314282, 0.26197383, 0.09014768, 0.19696823, -0.41025418, -0.08308115, -0.33279485, -0.22528782, 0.06172439, -0.1365661, -0.13094363, -0.005086559, 0.089024484) * go_1(0.0, 0.0);\n    result += mat4(0.05262993, 0.0006296959, 0.1657725, -0.32591924, 0.12126701, 0.061543245, -0.10526848, 0.041583937, 0.094976954, 0.09416157, -0.22019257, -0.058390073, -0.2073888, 0.057273377, 0.19558284, 0.004208022) * go_1(0.0, 1.0);\n    result += mat4(0.30005738, 0.18478931, -0.23342943, 0.22455733, -0.016488122, 0.099634305, 0.31620836, -0.15731157, 0.09595808, 0.0013774688, 0.48273298, -0.07027936, -0.18764344, -0.26194447, -0.11794225, -0.012173601) * go_1(1.0, -1.0);\n    result += mat4(0.117986746, -0.13846518, -0.019614812, -0.3011192, 0.5501164, 0.3408611, -0.40090847, 0.15706886, 0.13050972, 0.051776595, 0.20792943, 0.23389706, -0.22965533, -0.053367328, 0.3911586, -0.032988597) * go_1(1.0, 0.0);\n    result += mat4(0.054753624, -0.008485731, -0.2451672, 0.17528129, 0.13657846, 0.010480436, 0.07651423, -0.43316832, 0.12736236, 0.13804524, 0.12529011, -0.30946237, -0.14423579, 0.08403089, 0.24335162, 0.057288036) * go_1(1.0, 1.0);\n    result += vec4(0.012077211, 0.013045883, 0.0380778, -0.02908858);\n    return result;\n}\n//!DESC Anime4K-v3.2-Upscale-CNN-x2-(S)-Conv-4x3x3x8\n//!HOOK MAIN\n//!BIND conv2d_1_tf\n//!SAVE conv2d_2_tf\n//!WIDTH conv2d_1_tf.w\n//!HEIGHT conv2d_1_tf.h\n//!COMPONENTS 4\n//!WHEN OUTPUT.w MAIN.w / 1.200 > OUTPUT.h MAIN.h / 1.200 > *\n#define go_0(x_off, y_off) (max((conv2d_1_tf_texOff(vec2(x_off, y_off))), 0.0))\n#define go_1(x_off, y_off) (max(-(conv2d_1_tf_texOff(vec2(x_off, y_off))), 0.0))\nvec4 hook() {\n    vec4 result = mat4(-0.036115196, -0.06971895, -0.07508942, 0.016036168, 0.12120111, 0.24536026, 0.044755507, -0.20663576, 0.029635755, -0.15427187, 0.027148994, -0.20795093, 0.10170582, 0.077919215, 0.66063017, -0.4632968) * go_0(-1.0, -1.0);\n    result += mat4(-0.0052889925, -0.019060908, -0.08660142, -0.022095207, -0.08097976, -0.015142803, -0.18552722, -0.078493506, -0.16293915, -0.20099808, -0.08370822, 0.3701389, 0.09094984, 0.2487225, 0.24338846, 0.044003833) * go_0(-1.0, 0.0);\n    result += mat4(-0.061406493, -0.017232792, -0.10917424, 0.11203319, 0.040699825, -0.019294346, 0.084953666, -0.018133596, 0.07209552, 0.016069936, 0.17805555, -0.089537814, 0.15809004, 0.1027023, 0.15044671, -0.15530108) * go_0(-1.0, 1.0);\n    result += mat4(0.0948676, -0.040305693, -0.005591629, -0.048048403, -0.07547777, 0.056606572, 0.021390207, 0.32600567, -0.20805131, -0.099587254, 0.029613169, 0.0092129605, -0.29429698, -0.09898621, 0.44470885, -0.89487344) * go_0(0.0, -1.0);\n    result += mat4(-0.122259885, 0.11445877, 0.06666907, 0.1869428, -0.1553992, -0.1658741, 0.2988138, -0.57746625, -0.34609964, 0.11169158, -0.41877756, 0.38075635, 0.21293911, 0.09640372, -0.12754214, -0.08026104) * go_0(0.0, 0.0);\n    result += mat4(0.15128808, 0.050087795, 0.09219755, -0.18080945, 0.0044571217, -0.046019405, -0.1289922, 0.20305426, 0.19601224, 0.04667917, 0.17465587, 0.027672665, 0.18441725, 0.06845396, 0.11288585, -0.23283863) * go_0(0.0, 1.0);\n    result += mat4(-0.072962, -0.06639447, 0.049347494, -0.1386401, 0.10396071, 0.08187777, -0.04280746, 0.07390891, 0.06628344, 0.037797406, 0.021885803, -0.013147403, 0.22376558, 0.36243078, 0.12874891, -0.0023783944) * go_0(1.0, -1.0);\n    result += mat4(0.074945286, 0.16045591, -0.11798349, 0.12910712, 0.054760084, -0.095626175, -0.047832094, 0.03493912, 0.11817307, 0.037452437, -0.14301221, -0.027356789, -0.052390423, 0.11373512, 0.07686775, 0.010008694) * go_0(1.0, 0.0);\n    result += mat4(-0.023999173, -0.091900624, 0.02388157, 0.03173873, 0.0065633506, -0.033716757, -0.1198324, 0.12057766, 0.026465805, -0.07517131, -0.07760598, 0.060463097, 0.07345541, 0.046037503, 0.21101558, -0.26785463) * go_0(1.0, 1.0);\n    result += mat4(0.15544604, -0.03902825, 0.04630384, -0.25173616, -0.0691359, 0.07476507, 0.009071253, 0.089964196, -0.26539803, -0.3958477, -0.22155671, 0.20735882, -0.105860494, -0.003996804, -0.044815883, 0.39544627) * go_1(-1.0, -1.0);\n    result += mat4(0.6169709, 0.23717614, -0.37884676, -0.7484867, 0.020169826, -0.30718836, 1.0965588, -0.20711036, -0.39149985, -0.06843563, -0.06522909, 0.103805855, 0.03265825, -0.15137726, 0.12837899, -0.01294922) * go_1(-1.0, 0.0);\n    result += mat4(-0.23638196, -0.4560866, -0.11948684, -0.1464144, 0.10690008, 0.007835961, 0.11864342, -0.13101323, -0.16509797, 0.075027354, 0.08122998, 0.13451207, 0.0011890623, 0.052157886, 0.08372405, -0.07085038) * go_1(-1.0, 1.0);\n    result += mat4(-0.21997726, -0.16488647, -0.0291317, 0.17997476, 0.1493211, 0.027494298, 0.0034613227, -0.3207727, 0.18699001, 0.14728633, -0.042895135, -0.07612043, 0.125076, -0.14714554, -0.03480009, -0.22753975) * go_1(0.0, -1.0);\n    result += mat4(-0.5342686, -0.7426105, -0.38294584, 0.42549992, 0.46053204, 0.7867879, 0.106234804, -0.041163098, 0.5198579, -0.5219404, 0.14809476, -0.41802374, 0.06810794, -0.15122683, -0.047409, 0.13178343) * go_1(0.0, 0.0);\n    result += mat4(-0.50428164, 0.18220626, 0.35510704, -0.081787474, 0.03155813, 0.019284263, 0.0032388573, -0.20513348, -0.05385551, 0.17803182, -0.26206362, 0.2870375, 0.008557827, 0.08401449, -0.027598893, -0.010791235) * go_1(0.0, 1.0);\n    result += mat4(0.16657415, 0.067647465, 0.093076974, -0.14438486, -0.10017002, 0.0022367141, 0.03250936, -0.052794546, -0.009178676, -0.019673595, -0.0016697067, -0.15424626, -0.112123474, -0.11079971, 0.011987111, -0.11747758) * go_1(1.0, -1.0);\n    result += mat4(-0.023021797, -0.058703423, -0.037978355, -0.062433913, -0.13130441, 0.048656322, 0.056839373, 0.109036915, -0.07823158, 0.14785293, 0.058555078, -0.11679035, -0.14002073, 0.07395252, 0.098268874, -0.06710464) * go_1(1.0, 0.0);\n    result += mat4(0.14906375, 0.030001195, -0.10338215, 0.0662968, -0.161953, -0.13682815, 0.09563142, 0.009514228, -0.009491218, 0.06737101, -0.1393389, 0.15231515, -0.073147796, 0.00767062, 0.028675212, 0.014213088) * go_1(1.0, 1.0);\n    result += vec4(0.018736731, -0.0026039074, 0.050130025, -0.055364225);\n    return result;\n}\n//!DESC Anime4K-v3.2-Upscale-CNN-x2-(S)-Conv-4x3x3x8\n//!HOOK MAIN\n//!BIND conv2d_2_tf\n//!SAVE conv2d_last_tf\n//!WIDTH conv2d_2_tf.w\n//!HEIGHT conv2d_2_tf.h\n//!COMPONENTS 4\n//!WHEN OUTPUT.w MAIN.w / 1.200 > OUTPUT.h MAIN.h / 1.200 > *\n#define go_0(x_off, y_off) (max((conv2d_2_tf_texOff(vec2(x_off, y_off))), 0.0))\n#define go_1(x_off, y_off) (max(-(conv2d_2_tf_texOff(vec2(x_off, y_off))), 0.0))\nvec4 hook() {\n    vec4 result = mat4(0.019100675, -0.014241565, 0.004667036, -0.03865062, 0.106731094, 0.026099661, 0.014594411, -0.011881356, 0.0040967264, -0.004626336, 0.006469508, 0.010875305, -0.033909045, -0.085905954, 0.07861378, 0.019452631) * go_0(-1.0, -1.0);\n    result += mat4(0.20777655, -0.060354974, 0.0023840065, -0.064121604, -0.17397617, 0.019293457, -0.09707183, 0.080641985, 0.01025124, -0.017382381, 0.008661793, -0.010995665, 0.21943407, -0.115574986, 0.14471593, -0.068836235) * go_0(-1.0, 0.0);\n    result += mat4(0.057942886, -0.06311754, 0.2253396, -0.04159292, -0.020731755, 0.007877151, 0.041525815, 0.025278691, 0.03041967, -0.025137542, 0.024364179, -0.024543528, 0.029438615, -0.015506873, 0.081686, -0.07812221) * go_0(-1.0, 1.0);\n    result += mat4(0.054237515, 0.0676094, -0.0047708177, 0.0043467237, -0.10032304, -0.020498628, 0.04240586, 0.07272254, 0.0784221, 0.017945962, -0.022310399, -0.013134622, 0.015638694, -0.10001543, 0.1043031, 0.05898838) * go_0(0.0, -1.0);\n    result += mat4(-0.021652509, 0.35796642, 0.059497777, 0.23948468, 0.15454951, -0.10017235, -0.19072174, -0.44812536, -0.03974552, 0.04529369, 0.22207436, 0.026222564, -0.09705454, 0.5623026, -0.3354105, -0.017278556) * go_0(0.0, 0.0);\n    result += mat4(-0.053682446, -0.03411237, -0.09399936, 0.15128824, -0.07463, -0.042020727, 0.0031783928, 0.13481957, -0.07731454, 0.044114403, -0.23085599, 0.060444202, -0.15015422, 0.0018040676, -0.18684982, 0.2812511) * go_0(0.0, 1.0);\n    result += mat4(0.0029329916, 0.001596018, 0.0007512241, 0.016544111, -0.04876942, -0.05272409, 0.037884697, 0.049948208, 0.015518177, 0.11368592, -0.03815777, -0.013149978, -0.027638039, 0.107719295, -0.04115787, 0.02745414) * go_0(1.0, -1.0);\n    result += mat4(0.016691081, 0.010204119, 0.04078854, 0.01613337, 0.03325829, 0.0114824055, -0.017286912, -0.07284126, -0.110984206, -0.21041764, 0.0089543555, 0.18986733, 0.01537506, -0.2059135, 0.029074017, 0.013117443) * go_0(1.0, 0.0);\n    result += mat4(0.013965926, 0.029871881, 0.0034499036, -0.011343668, 0.022120327, -0.0068748263, 0.009324342, -0.039081004, 0.08032371, 0.050809264, 0.035050742, -0.2032847, 0.06305391, -0.021958945, 0.038569167, -0.22465245) * go_0(1.0, 1.0);\n    result += mat4(0.046307724, -0.012419472, 0.007673863, -0.042344846, 0.011042414, 0.016994251, -0.018166406, -0.016955731, -0.13240299, 0.01768431, -0.027607648, 0.0699927, -0.02840628, 0.004414203, 0.0049618417, 0.011084679) * go_1(-1.0, -1.0);\n    result += mat4(-0.119954154, -0.007455482, -0.031108133, -0.009946449, 0.0077065965, 0.01660345, 0.032943666, 0.016376585, 0.10273124, 0.1556573, -0.24643841, 0.107307844, -0.068235755, 0.0561896, -0.0104672015, 0.042693343) * go_1(-1.0, 0.0);\n    result += mat4(-0.01634601, 0.04195375, -0.10401894, 0.047641944, -0.034602515, -0.0034419263, -0.010457858, 0.015194475, -0.03962551, -0.030031368, 0.16036317, 0.019283568, -0.05877721, 0.016504882, -0.15523468, 0.018161612) * go_1(-1.0, 1.0);\n    result += mat4(-0.08083991, 0.0024665035, -0.049373373, 0.030371357, 0.0113322195, -0.014676956, 0.011646689, -0.01142667, 0.124930486, 0.06625774, -0.045840867, -0.009693036, -0.012649251, -0.07388084, 0.008790075, 0.0013844534) * go_1(0.0, -1.0);\n    result += mat4(-0.33941835, -0.2763476, -0.118311435, -0.063535266, 0.20936015, 0.13731301, 0.13443594, 0.07464433, 0.059650812, -0.36973104, 0.16444235, -0.37082872, 0.06432777, -0.18283032, -0.044489607, -0.13895285) * go_1(0.0, 0.0);\n    result += mat4(0.13533665, 0.08268915, -0.03675727, -0.14348659, 0.0186255, -0.05051692, 0.056702953, 0.0061717895, 0.047663026, -0.088188455, 0.23254345, -0.014015464, 0.08400204, -0.0073777726, 0.2202068, -0.12366078) * go_1(0.0, 1.0);\n    result += mat4(0.04361004, 0.046543695, 0.0064863074, -0.03358146, -0.022602187, 0.018138997, -0.011071864, 0.010244091, -0.019814799, -0.17250171, 0.040823266, -0.040131986, 0.010125854, 0.020660749, 0.0020435036, -0.010819304) * go_1(1.0, -1.0);\n    result += mat4(-0.004810193, -0.11286074, 0.051985834, 0.04788631, -0.023950428, 0.036145125, -0.038203828, 0.052401308, 0.022986965, 0.26420745, -0.06076917, -0.09252999, 0.03164547, 0.15652153, -0.037934, -0.0035418556) * go_1(1.0, 0.0);\n    result += mat4(0.03358366, -0.005219482, 0.007060882, -0.06569114, -0.02941682, 0.00966056, -0.0153679885, 0.019905418, -0.107232265, -0.03405676, -0.044340115, 0.26892832, -0.04723829, -0.02589829, 0.004563232, 0.19318114) * go_1(1.0, 1.0);\n    result += vec4(-0.00346731, -0.0046263863, -0.004627155, -0.0057769152);\n    return result;\n}\n//!DESC Anime4K-v3.2-Upscale-CNN-x2-(S)-Depth-to-Space\n//!HOOK MAIN\n//!BIND MAIN\n//!BIND conv2d_last_tf\n//!SAVE MAIN\n//!WIDTH conv2d_last_tf.w 2 *\n//!HEIGHT conv2d_last_tf.h 2 *\n//!WHEN OUTPUT.w MAIN.w / 1.200 > OUTPUT.h MAIN.h / 1.200 > *\nvec4 hook() {\n    vec2 f0 = fract(conv2d_last_tf_pos * conv2d_last_tf_size);\n    ivec2 i0 = ivec2(f0 * vec2(2.0));\n    float c0 = conv2d_last_tf_tex((vec2(0.5) - f0) * conv2d_last_tf_pt + conv2d_last_tf_pos)[i0.y * 2 + i0.x];\n    float c1 = c0;\n    float c2 = c1;\n    float c3 = c2;\n    return vec4(c0, c1, c2, c3) + MAIN_tex(MAIN_pos);\n}\n"
  },
  {
    "path": "app/src/main/assets/shaders/Anime4K_Upscale_CNN_x2_VL.glsl",
    "content": "// MIT License\n\n// Copyright (c) 2019-2021 bloc97\n// All rights reserved.\n\n// Permission is hereby granted, free of charge, to any person obtaining a copy\n// of this software and associated documentation files (the \"Software\"), to deal\n// in the Software without restriction, including without limitation the rights\n// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell\n// copies of the Software, and to permit persons to whom the Software is\n// furnished to do so, subject to the following conditions:\n\n// The above copyright notice and this permission notice shall be included in all\n// copies or substantial portions of the Software.\n\n// THE SOFTWARE IS PROVIDED \"AS IS\", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR\n// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,\n// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE\n// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER\n// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,\n// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE\n// SOFTWARE.\n\n//!DESC Anime4K-v3.2-Upscale-CNN-x2-(VL)-Conv-4x3x3x3\n//!HOOK MAIN\n//!BIND MAIN\n//!SAVE conv2d_tf\n//!WIDTH MAIN.w\n//!HEIGHT MAIN.h\n//!COMPONENTS 4\n//!WHEN OUTPUT.w MAIN.w / 1.200 > OUTPUT.h MAIN.h / 1.200 > *\n#define go_0(x_off, y_off) (MAIN_texOff(vec2(x_off, y_off)))\nvec4 hook() {\n    vec4 result = mat4(0.3053028, -0.037464816, 0.113983095, 0.12537485, -0.18630321, 0.084269725, -0.01351514, -0.20190673, -0.12298384, -0.037622184, -0.070214555, -0.19367279, 0.0, 0.0, 0.0, 0.0) * go_0(-1.0, -1.0);\n    result += mat4(-0.41849324, 0.099702746, -0.04276645, -0.047299717, 0.20074473, 0.14217933, 0.15571699, 0.19553481, 0.21868695, -0.053848714, 0.016413521, 0.14117444, 0.0, 0.0, 0.0, 0.0) * go_0(-1.0, 0.0);\n    result += mat4(0.030540446, -0.052293833, 0.0715466, -0.31160545, 0.07808315, -0.16860045, 0.032828577, -0.2955024, -0.110374965, 0.04043687, -0.014024628, 0.058699366, 0.0, 0.0, 0.0, 0.0) * go_0(-1.0, 1.0);\n    result += mat4(-0.10727635, 0.054200135, 0.20853694, 0.21086875, 0.122690216, -0.091823794, 0.310609, -0.01738923, -0.0013488946, 0.10835534, -0.077265196, 0.086751856, 0.0, 0.0, 0.0, 0.0) * go_0(0.0, -1.0);\n    result += mat4(-0.77150255, 0.40530515, -0.41257596, -0.14367618, 0.46888494, 0.2650122, -0.934199, 0.40476102, 0.32293493, 0.20251967, 0.19891106, -0.29698747, 0.0, 0.0, 0.0, 0.0) * go_0(0.0, 0.0);\n    result += mat4(-0.12505147, -0.41904053, -0.065798186, 0.34075752, 0.026240354, -0.2977496, 0.032647505, -0.003566783, 0.10290523, -0.23417123, -0.06014203, 0.094735645, 0.0, 0.0, 0.0, 0.0) * go_0(0.0, 1.0);\n    result += mat4(0.11207838, -0.04062474, 0.023897955, 0.08605987, -0.020888371, 0.045541205, -0.07231824, -0.25884083, -0.11796847, -0.002691391, 0.0050435597, 0.02756291, 0.0, 0.0, 0.0, 0.0) * go_0(1.0, -1.0);\n    result += mat4(0.4615728, 0.041790638, 0.08971143, 0.20213957, -0.38537467, 0.19938901, 0.08594364, -0.08621994, -0.08163473, -0.133266, -0.09561729, -0.014209637, 0.0, 0.0, 0.0, 0.0) * go_0(1.0, 0.0);\n    result += mat4(0.0787417, -0.0483673, 0.07621572, -0.060169693, -0.013465177, -0.17152289, 0.02515561, 0.17675288, -0.05173998, 0.10768042, -0.029858522, -0.013957215, 0.0, 0.0, 0.0, 0.0) * go_0(1.0, 1.0);\n    result += vec4(0.0072128535, -0.05658625, 0.052939568, -0.1760861);\n    return result;\n}\n//!DESC Anime4K-v3.2-Upscale-CNN-x2-(VL)-Conv-4x3x3x3\n//!HOOK MAIN\n//!BIND MAIN\n//!SAVE conv2d_tf1\n//!WIDTH MAIN.w\n//!HEIGHT MAIN.h\n//!COMPONENTS 4\n//!WHEN OUTPUT.w MAIN.w / 1.200 > OUTPUT.h MAIN.h / 1.200 > *\n#define go_0(x_off, y_off) (MAIN_texOff(vec2(x_off, y_off)))\nvec4 hook() {\n    vec4 result = mat4(-0.112743355, 0.0422517, 0.21350034, -0.0967133, 0.16265953, 0.0022497, 0.015078242, 0.08204187, 0.035236806, -0.0468228, -0.09464228, -0.001864949, 0.0, 0.0, 0.0, 0.0) * go_0(-1.0, -1.0);\n    result += mat4(0.25631642, -0.41485596, -0.16662048, 0.13201024, 0.057921384, 0.2240005, -0.30038536, -0.08305622, 0.2228756, 0.32263795, 0.10608189, -0.18616734, 0.0, 0.0, 0.0, 0.0) * go_0(-1.0, 0.0);\n    result += mat4(0.08997524, 0.11516871, 0.19212262, -0.035154644, 0.11612274, -0.04056247, 0.14974374, 0.029173585, -0.07629641, -0.14353512, 0.041081246, 0.20230265, 0.0, 0.0, 0.0, 0.0) * go_0(-1.0, 1.0);\n    result += mat4(0.2262286, 0.055954933, -0.14499907, 0.17314723, 0.16590612, -0.06688698, -0.11118816, -0.012938116, -0.043101817, 0.026133137, 0.2958395, 0.06543993, 0.0, 0.0, 0.0, 0.0) * go_0(0.0, -1.0);\n    result += mat4(-0.07311521, -0.3041244, -0.47978505, -0.6350967, -0.17432262, 0.34965977, 0.25399777, -0.16590433, -0.49957857, 0.0549526, -0.40869385, -0.08780993, 0.0, 0.0, 0.0, 0.0) * go_0(0.0, 0.0);\n    result += mat4(-0.3014447, -0.00021343959, -0.14953177, 0.028001398, -0.14931908, -0.14910097, -0.13287953, -0.45026535, 0.17378895, 0.024704922, -0.027308129, -0.10292025, 0.0, 0.0, 0.0, 0.0) * go_0(0.0, 1.0);\n    result += mat4(-0.06732655, -0.13119644, 0.066014715, 0.081011154, -0.15154321, 0.2407805, 0.07733481, 0.12312706, 0.1741804, 0.008495716, -0.14125362, -0.043644864, 0.0, 0.0, 0.0, 0.0) * go_0(1.0, -1.0);\n    result += mat4(0.11465958, 0.42001364, 0.011069392, 0.3203028, -0.058801666, -0.37830314, -0.030540617, 0.2245139, -0.11310525, -0.14845212, 0.19957744, 0.25789997, 0.0, 0.0, 0.0, 0.0) * go_0(1.0, 0.0);\n    result += mat4(-0.16037206, 0.21326372, 0.020099448, 0.018666709, 0.122083254, -0.16033986, -0.10725163, 0.2556128, 0.1650688, -0.10475823, 0.048623525, -0.103755645, 0.0, 0.0, 0.0, 0.0) * go_0(1.0, 1.0);\n    result += vec4(0.007717166, -0.027800834, 0.0795002, 0.0053199283);\n    return result;\n}\n//!DESC Anime4K-v3.2-Upscale-CNN-x2-(VL)-Conv-4x3x3x16\n//!HOOK MAIN\n//!BIND conv2d_tf\n//!BIND conv2d_tf1\n//!SAVE conv2d_1_tf\n//!WIDTH conv2d_tf.w\n//!HEIGHT conv2d_tf.h\n//!COMPONENTS 4\n//!WHEN OUTPUT.w MAIN.w / 1.200 > OUTPUT.h MAIN.h / 1.200 > *\n#define go_0(x_off, y_off) (max((conv2d_tf_texOff(vec2(x_off, y_off))), 0.0))\n#define go_1(x_off, y_off) (max((conv2d_tf1_texOff(vec2(x_off, y_off))), 0.0))\n#define go_2(x_off, y_off) (max(-(conv2d_tf_texOff(vec2(x_off, y_off))), 0.0))\n#define go_3(x_off, y_off) (max(-(conv2d_tf1_texOff(vec2(x_off, y_off))), 0.0))\nvec4 hook() {\n    vec4 result = mat4(-0.0056740534, -0.21186607, -0.18014967, 0.118979976, -0.0015611284, -0.07708486, 0.060131397, 0.11653345, 0.027150517, 0.10837246, 0.08583816, -0.14032431, 0.017552888, 0.0035846964, 0.03980114, 0.064649396) * go_0(-1.0, -1.0);\n    result += mat4(-0.03289318, -0.12004539, 0.26514888, -0.15079662, 0.04214227, -0.027273783, -0.027950313, 0.19614808, 0.18510003, -0.10346252, -0.029836183, 0.09174428, -0.0088710375, -0.18273513, 0.06601674, 0.009983851) * go_0(-1.0, 0.0);\n    result += mat4(0.08476211, 0.043996535, 0.056711517, 0.009976895, 0.07039107, -0.024862664, -0.059921104, 0.046850603, 0.04983447, 0.04863198, 0.21777405, -0.0576961, 0.045321796, -0.0060038245, 0.096396215, -0.10842004) * go_0(-1.0, 1.0);\n    result += mat4(-0.15746164, 0.041757874, 0.035169285, -0.1734288, -0.24219254, -0.13318908, 0.2272079, -0.02902605, 0.07750601, -0.1467191, -0.12296749, -0.07533314, -0.07073083, 0.17909113, 0.04789308, 0.17245363) * go_0(0.0, -1.0);\n    result += mat4(0.057547905, 0.1464685, -0.33115456, -0.26956198, -0.26298407, -0.059824817, 0.022509675, -0.09251868, 0.36277944, -0.2072429, 0.21095088, -0.45492023, 0.07428653, 0.1593302, -0.2945834, 0.12825087) * go_0(0.0, 0.0);\n    result += mat4(-0.1318458, 0.27804148, 0.037600737, 0.12047866, 0.0065036337, 0.0017241207, 0.060497303, -0.14786585, -0.15149063, 0.02731698, 0.048886403, -0.0025970868, -0.026979815, 0.07348884, 0.015636757, -0.107966796) * go_0(0.0, 1.0);\n    result += mat4(-0.079988025, -0.01626299, 0.06517438, 0.086406484, -0.1484504, 0.070595, 0.20620634, 0.09713373, -0.13620836, 0.012067949, -0.00068703433, -0.038030174, 0.22300471, -0.0012400965, -0.014827909, -0.08927486) * go_0(1.0, -1.0);\n    result += mat4(0.15634936, 0.052028038, 0.038081627, 0.12720168, 0.07342066, -0.04318368, -0.0065998454, 0.12109317, -0.45398173, 0.03666754, -0.17773737, 0.038516667, -0.13009632, -0.007457001, -0.013938809, 0.09776142) * go_0(1.0, 0.0);\n    result += mat4(0.029636936, 0.12864171, 0.11347291, -0.11812842, -0.0870342, 0.035678383, 0.050338242, 0.045754932, -0.07072752, 0.010447726, 0.039642975, -0.08795004, -0.1191525, 0.00967509, 0.13485421, -0.053204738) * go_0(1.0, 1.0);\n    result += mat4(-0.011072695, -0.09613245, -0.09094804, 0.028029291, -0.04031162, 0.15690295, 0.25094184, -0.21776834, 0.06524669, 0.06412185, -0.052852992, -0.08097702, -0.039127756, 0.036357917, 0.104585476, 0.25095442) * go_1(-1.0, -1.0);\n    result += mat4(-0.08328618, -0.006246033, 0.099708706, -0.014916097, 0.17727195, 0.4369228, 0.14760216, 0.06707674, 0.025167737, -0.022487842, -0.038962565, 0.15380669, 0.08125089, 0.09844594, 0.33538374, -0.003161368) * go_1(-1.0, 0.0);\n    result += mat4(-0.0128195705, -0.05475118, -0.037705053, -0.0012077648, -0.17425515, 0.091487505, -0.12909423, 0.0074876705, 0.13438368, 5.778033e-05, 0.04563314, -0.12185897, -0.053612474, -0.049824294, -0.12851205, 0.12856449) * go_1(-1.0, 1.0);\n    result += mat4(-0.025741795, 0.01867236, -0.00027440622, 0.10502768, 0.27042285, -0.14947751, 0.11143123, 0.2575913, -0.07414089, -0.33919522, -0.13194235, -0.20088726, 0.23121537, -0.08197353, 0.06693911, 0.015411386) * go_1(0.0, -1.0);\n    result += mat4(0.09143717, 0.22842278, 0.06501074, -0.20009698, -0.042117566, -0.23452093, -0.074082755, -0.10612558, 0.077631965, 0.08343657, -0.07657599, -0.43297377, 0.7092466, -0.16272525, 0.17222248, -0.056038965) * go_1(0.0, 0.0);\n    result += mat4(0.081200436, 0.046752565, 0.028254949, 0.18820632, 0.096592255, 0.05896745, 0.14845169, 0.034777895, 0.07195204, -0.1908046, -0.015341971, 0.02606145, -0.010377239, 0.0755547, -0.15285216, 0.047916733) * go_1(0.0, 1.0);\n    result += mat4(-0.06825636, -0.049540907, -0.024328846, 0.03506251, 0.2060094, 0.054119263, -0.06671269, 0.052428722, 0.055792283, -0.14336903, -0.03180757, 0.013760968, -0.037398104, -0.06880077, -0.023608573, 0.0360965) * go_1(1.0, -1.0);\n    result += mat4(-0.16937497, -0.30156836, 0.0021435453, 0.025772978, -0.17990975, 0.046133514, -0.32447076, -0.083382785, -0.081322014, -0.022132374, -0.05319431, 0.11794733, 0.08943906, 0.12927428, 0.105764806, -0.051034793) * go_1(1.0, 0.0);\n    result += mat4(-0.011012306, 0.047636557, 0.050260928, 0.051847618, 0.010985655, -0.13752967, 0.023869954, 0.07011459, -0.18244945, 0.07239806, -0.013638856, -0.026982805, 0.11395993, -0.031304818, -0.08714153, 0.077115685) * go_1(1.0, 1.0);\n    result += mat4(0.08707592, 0.2265186, 0.13363098, -0.039588258, -0.029561255, 0.019238092, 0.024606103, -0.0019022018, -0.062285982, -0.0629511, -0.03753033, 0.109805316, 0.016018672, -0.08284564, -0.04092752, -0.030386891) * go_2(-1.0, -1.0);\n    result += mat4(0.0016500859, 0.01616536, -0.099148355, 0.24161765, 0.028064307, -0.028680569, 0.054400917, -0.1978921, -0.08584302, -0.096797146, -0.06546965, -0.09342837, 0.030265866, 0.07057579, -0.02080932, 0.053178705) * go_2(-1.0, 0.0);\n    result += mat4(-0.030304352, 0.047440585, -0.04248429, 0.08568772, -0.051317703, 0.036739342, 0.00865767, -0.018183297, -0.07335176, 0.025001721, -0.068509035, 0.1814819, -0.09756565, -0.024179723, -0.05959287, 0.0352454) * go_2(-1.0, 1.0);\n    result += mat4(0.023015196, -0.022870664, -0.12028372, -0.111095205, 0.11065281, -0.19900022, -0.24012049, -0.017028643, -0.13484617, 0.050107025, 0.10741765, 0.037951697, 0.013090438, -0.0010045726, -0.029447839, -0.1859787) * go_2(0.0, -1.0);\n    result += mat4(0.17922719, -0.24138594, -0.44595388, -0.032014426, 0.06897096, 0.07125395, 0.1944457, -0.035794795, -0.24022278, -0.13230884, -0.1277025, 0.21229011, -0.12249393, 0.06141907, 0.2687936, -0.26896995) * go_2(0.0, 0.0);\n    result += mat4(0.0397242, -0.30710965, 0.28815824, -0.06642567, -0.07588877, -0.019552408, 0.0057806037, 0.11465521, 0.03560534, -0.10640553, 0.023589289, -0.16667193, 0.02066607, -0.01026633, -0.02655378, 0.082493655) * go_2(0.0, 1.0);\n    result += mat4(-0.007902949, -0.08501038, -0.029395591, -0.07072227, -0.01800967, -0.14564751, -0.08372804, -0.049974415, 0.1756957, -0.02042449, -0.04413007, -0.016873527, -0.2385717, -0.001741017, 0.08298281, -0.019873247) * go_2(1.0, -1.0);\n    result += mat4(-0.01803727, 0.0642893, 0.21513617, 0.066888265, -0.042107955, -0.123470366, 0.045296013, -0.11958806, 0.48208967, -0.027188249, 0.12136116, 0.05246265, 0.13522038, -0.016297493, 0.028486907, -0.059840377) * go_2(1.0, 0.0);\n    result += mat4(-0.1373251, -0.11281026, -0.06418318, 0.08444032, 0.062874556, -0.009133875, -0.049571835, -0.042995855, 0.12483249, -0.025967957, -0.11202483, 0.09862257, 0.099986054, 0.009230306, -0.09042664, 0.046612263) * go_2(1.0, 1.0);\n    result += mat4(0.03203309, 0.106030256, 0.045741174, -0.020529225, -0.028610658, -0.055219248, -0.21404657, 0.07746393, -0.059359375, 0.0033258004, -0.0054513607, 0.06856653, 0.18043655, -0.119936846, -0.05639265, -0.10240379) * go_3(-1.0, -1.0);\n    result += mat4(-0.0004331875, 0.10426754, -0.008130048, 0.012795991, -0.14372933, -0.40797862, 0.105197415, -0.0041354536, -0.079792455, 0.0914027, 0.012418237, -0.11449173, 0.020261409, -0.14681602, -0.13355242, 0.18290488) * go_3(-1.0, 0.0);\n    result += mat4(0.052306626, 0.010864275, -0.072627716, -0.009773121, 0.09484167, -0.09631301, 0.14896165, -0.21220942, -0.11994051, -0.002957136, -0.118194886, 0.08661347, 0.10005298, -0.029620873, 0.101668894, 0.0242806) * go_3(-1.0, 1.0);\n    result += mat4(-0.055188183, -0.06322889, 0.12994595, 0.03140751, -0.092755616, 0.04239107, 0.18460171, 0.08471877, 0.014203371, 0.13608724, 0.035351243, -0.07883493, -0.10067456, 0.14417742, 0.0054235114, 0.100745104) * go_3(0.0, -1.0);\n    result += mat4(-0.043811034, -0.16055201, -0.11927185, 0.20517266, 0.16734722, 0.27720267, 0.1205665, 0.045803893, -0.07874647, 0.06764307, -0.11157022, 0.080770165, -0.044105835, -0.03276538, -0.10945451, 0.100562036) * go_3(0.0, 0.0);\n    result += mat4(-0.044731796, -0.12854387, -0.061937924, -0.21604767, -0.036132332, -0.024353411, -0.16718283, 0.14903957, -0.11620588, 0.14563644, 0.23363836, 0.08400659, 0.15248756, -0.1424437, 0.112882614, -0.04096889) * go_3(0.0, 1.0);\n    result += mat4(-0.0486021, -0.05714939, 0.042517707, -0.06106919, -0.12970918, -0.071898215, -0.044727243, -0.026308542, 0.05687118, -0.0394057, -0.109454155, -0.0021216893, 0.018588595, 0.08061093, 0.0500373, -0.0034918839) * go_3(1.0, -1.0);\n    result += mat4(0.11269324, -0.17924047, -0.12965205, -0.07287767, -0.015830642, -0.044497102, 0.20014328, -0.14054494, 0.1232692, 0.2395109, 0.14093149, 0.03518561, -0.14088139, -0.09045081, -0.07283352, 0.053434785) * go_3(1.0, 0.0);\n    result += mat4(0.020512339, 0.026349569, -0.06666101, 0.05554806, -0.03044066, 0.26656216, 0.019155584, -0.12118906, 0.087923005, -0.1716557, 0.050843164, 0.037432503, -0.030232614, 0.030457936, 0.04232163, -0.066400655) * go_3(1.0, 1.0);\n    result += vec4(-0.0216415, 0.09015036, -0.030761974, -0.26541537);\n    return result;\n}\n//!DESC Anime4K-v3.2-Upscale-CNN-x2-(VL)-Conv-4x3x3x16\n//!HOOK MAIN\n//!BIND conv2d_tf\n//!BIND conv2d_tf1\n//!SAVE conv2d_1_tf1\n//!WIDTH conv2d_tf.w\n//!HEIGHT conv2d_tf.h\n//!COMPONENTS 4\n//!WHEN OUTPUT.w MAIN.w / 1.200 > OUTPUT.h MAIN.h / 1.200 > *\n#define go_0(x_off, y_off) (max((conv2d_tf_texOff(vec2(x_off, y_off))), 0.0))\n#define go_1(x_off, y_off) (max((conv2d_tf1_texOff(vec2(x_off, y_off))), 0.0))\n#define go_2(x_off, y_off) (max(-(conv2d_tf_texOff(vec2(x_off, y_off))), 0.0))\n#define go_3(x_off, y_off) (max(-(conv2d_tf1_texOff(vec2(x_off, y_off))), 0.0))\nvec4 hook() {\n    vec4 result = mat4(0.04688368, 0.13853125, 0.1714716, -0.03034447, -0.08090605, 0.1225867, 0.17535992, 0.012508419, -0.0010665918, -0.07481546, -0.15541986, 0.0671128, -0.029307734, -0.076674186, 0.03925896, -0.07140553) * go_0(-1.0, -1.0);\n    result += mat4(-0.13273083, 0.062933214, 0.04200143, -0.0080243945, -0.120439716, -0.090192355, -0.022639645, 0.00020024918, -0.11211478, -0.12949537, 0.025783822, 0.009155746, 0.01004339, -0.0661901, 0.10630156, 0.053137038) * go_0(-1.0, 0.0);\n    result += mat4(0.07113487, -0.16011865, -0.10838903, -0.0034704183, 0.110606894, -0.14915739, 0.036511585, -0.003103608, -0.0551775, -0.13140677, 0.05270299, 0.12139221, 0.02226174, 0.008415268, -0.06647426, 0.118130066) * go_0(-1.0, 1.0);\n    result += mat4(-0.045172617, -0.0020388453, -0.27287582, 0.002428232, -0.2833772, 0.13788106, 0.073339015, 0.10666715, 0.08455194, 0.16499293, 0.089058325, 0.008815447, 0.034657538, -0.109856166, -0.11499077, -0.02918854) * go_0(0.0, -1.0);\n    result += mat4(0.07910854, -0.26334837, -0.3246593, -0.08246522, 0.09211476, 0.40793833, -0.09658794, -0.14430091, -0.50632644, 0.087234974, 0.26298127, 0.3687086, 0.06492316, 0.23082961, 0.18233871, -0.09283792) * go_0(0.0, 0.0);\n    result += mat4(-0.022744032, 0.21690565, 0.2694824, -0.12230013, -0.07969618, 0.21595429, -0.034979805, 0.008938489, 0.21289209, -0.446482, -0.042927746, -0.13587558, -0.032581557, -0.07182814, -0.054092336, -0.009542036) * go_0(0.0, 1.0);\n    result += mat4(-0.0034912943, -0.080354184, -0.08577375, -0.1521193, 0.09809233, 0.034529503, -0.100664355, 0.008191219, -0.014303411, -0.02862216, -0.18669915, -0.12384598, 0.046499267, 0.093707144, 0.10661308, 0.15079576) * go_0(1.0, -1.0);\n    result += mat4(-0.031025652, -0.0384342, 0.14258307, 0.25531343, 0.0075049917, -0.03966595, 0.062381975, 0.19593526, -0.2868182, 0.03162008, -0.4391041, -0.524017, -0.034463473, -0.0066741486, -0.24586639, 0.10521736) * go_0(1.0, 0.0);\n    result += mat4(-0.07452321, -0.0227877, -0.025402244, 0.115727395, -0.039511252, -0.07785703, -0.013689458, 0.0066024344, -0.052957747, 0.011206241, -0.0021671024, 0.077190824, -0.11709912, 0.046635598, 0.123751156, -0.03712064) * go_0(1.0, 1.0);\n    result += mat4(0.055411004, -0.0020031065, 0.06685547, -0.018829947, -0.06378933, -0.18389674, -0.0023551763, 0.0670314, 0.13038594, 0.0601923, -0.03035789, -0.019537423, -0.014483204, -0.056800704, 0.08663347, -0.106859975) * go_1(-1.0, -1.0);\n    result += mat4(-0.06603686, 0.07360526, -0.0072026253, -0.06778907, -0.039178446, 0.012397263, -0.13482279, 0.05745685, -0.055182382, -0.10545766, 0.003857615, 0.041947857, -0.15239377, 0.041826613, 0.058879383, -0.0042669442) * go_1(-1.0, 0.0);\n    result += mat4(-0.0697229, -0.010702144, -0.032265816, 0.013317131, 0.105028264, 0.21032134, 0.06845646, -0.018358687, 0.064568676, 0.08437135, -0.000723181, 0.1324007, 0.05527932, -0.049871888, -0.10125047, -0.005040889) * go_1(-1.0, 1.0);\n    result += mat4(-0.006467578, -0.05120533, -0.011780779, -0.011742203, -0.34242442, -0.020819988, 0.17381702, -0.059836414, -0.028882682, 0.23210457, 0.16579404, -0.03708216, -0.23541835, -0.03290251, 0.029319672, 0.26189178) * go_1(0.0, -1.0);\n    result += mat4(-0.30955994, -0.06408282, -0.16872866, 0.10767772, -0.041430887, 0.051697977, 0.12523535, -0.060389146, 0.026289431, 0.06359533, 0.13526368, 0.2479901, -0.3263977, 0.10216362, -0.0030894123, 0.046437826) * go_1(0.0, 0.0);\n    result += mat4(0.10061438, -0.17047118, -0.21593021, -0.023389054, -0.17507865, -0.30822313, -0.22044766, 0.16078933, 0.07099252, -0.11573018, 0.24712858, -0.0659458, -0.037504572, -0.12297423, 0.03342632, -0.058119852) * go_1(0.0, 1.0);\n    result += mat4(-0.020957774, -0.0224927, 0.04069268, -0.07911167, 0.074009344, 0.065916434, 0.008222278, 0.11625076, -0.25299504, 0.03357169, -0.021988, 0.015821831, -0.0021187372, -0.030700417, -0.004374924, 0.027358979) * go_1(1.0, -1.0);\n    result += mat4(0.06549052, -0.048067164, 0.05489091, -0.28851983, 0.13378961, 0.026875904, -0.09877994, -0.19947459, -0.1274035, -0.022928834, -0.26344195, -0.025870804, 0.022505255, 0.0070861108, 0.121051334, -0.025964163) * go_1(1.0, 0.0);\n    result += mat4(0.059426542, -0.0327433, 0.2313695, -0.07046268, 0.20479666, 0.027021704, 0.2564928, -0.11689885, -0.07407976, -0.019611249, 0.093463086, -0.121553615, 0.035009407, -0.008135333, -0.075931996, 0.047803063) * go_1(1.0, 1.0);\n    result += mat4(-0.059434246, -0.1652242, -0.124611154, 0.04743711, 0.10530296, -0.13869187, -0.036534663, -0.035206333, 0.06067593, 0.06126907, 0.120151915, -0.06722673, 0.008103894, 0.037225723, -0.007520425, 0.065720856) * go_2(-1.0, -1.0);\n    result += mat4(-3.6759695e-05, -0.036789574, 0.013370567, -0.037871476, -0.013454664, 0.15086569, 0.10164699, 0.057703357, -0.12871023, 0.12827681, -0.055057358, -0.040753044, -0.0142621, 0.08563361, -0.04615499, -0.03130452) * go_2(-1.0, 0.0);\n    result += mat4(-0.117965914, 0.09056485, 0.07272314, 0.009695964, -0.11331058, 0.07467256, -0.08291521, 0.00937355, -0.04097737, 0.07752905, -0.017335521, -0.12539999, 0.039462104, -0.0007037007, 0.06034812, -0.09497377) * go_2(-1.0, 1.0);\n    result += mat4(0.20828065, 0.0400099, 0.047638226, -0.046423353, -0.026133502, 0.098207295, 0.056742374, 0.017029466, -0.058164768, -0.046973787, -0.17328712, -0.0012984811, 0.050085854, 0.11296557, 0.12639083, 0.058543045) * go_2(0.0, -1.0);\n    result += mat4(-0.098907426, 0.22031747, 0.101559944, 0.06616554, 0.026110496, 0.56487054, 0.23754556, -0.07540935, 0.31768414, -0.47653618, 0.015073956, -0.33731326, 0.087285936, -0.24593173, -0.26141426, 0.15003823) * go_2(0.0, 0.0);\n    result += mat4(0.046026446, -0.13767281, 0.064847544, 0.07717139, 0.08544123, -0.11092969, 0.072325274, 0.010849038, -0.3055905, 0.66436774, 0.1434729, 0.0494463, 0.07115603, 0.083811216, 0.020431712, 0.06537088) * go_2(0.0, 1.0);\n    result += mat4(-0.15532711, 0.030139687, 0.040853374, 0.11089222, -0.08150315, -0.015851755, -0.06787692, 0.096075505, -0.011956207, -0.0017758606, 0.1277494, 0.16156575, -0.038588695, -0.0626418, -0.041797023, -0.19467135) * go_2(1.0, -1.0);\n    result += mat4(0.12917455, 0.017410474, -0.20125067, -0.08040003, -0.13494664, 0.17789102, -0.19909395, 0.08441434, 0.078570575, -0.06330619, 0.23767303, 0.5442659, -0.009227878, -0.021818208, 0.14318731, -0.09042824) * go_2(1.0, 0.0);\n    result += mat4(0.097801, 0.09345441, 0.17846581, -0.14773296, 0.06536365, 0.07642184, -0.011880635, 0.02086135, 0.013336972, -0.053295113, -0.13410404, 0.027241753, 0.087728985, -0.044033397, -0.13098569, 0.009423933) * go_2(1.0, 1.0);\n    result += mat4(-0.02488427, 0.0134966355, -0.0075000813, 0.07272353, 0.015842725, 0.13765687, 0.028079558, -0.08384948, -0.06666623, -0.023220664, 0.025091043, -0.055167805, -0.18826278, 0.04423603, 0.13499942, 0.059128854) * go_3(-1.0, -1.0);\n    result += mat4(0.01935146, -0.030980906, -0.031569187, -0.0036869382, 0.036753897, 0.118464164, 0.15871695, -0.09842428, 0.023324292, 0.071796335, -0.07869346, -0.10751301, -0.2588698, 0.064011686, 0.17386378, -0.039197855) * go_3(-1.0, 0.0);\n    result += mat4(0.08590827, 0.005497696, -0.026512025, 0.015661815, 0.1102415, -0.08268483, -0.0032903247, 0.10049029, -0.008157236, -0.035823178, -0.017570151, -0.081716835, -0.3531045, 0.010005245, 0.017141227, -0.016376914) * go_3(-1.0, 1.0);\n    result += mat4(-0.16617337, -0.007689783, 0.00954665, 0.07117733, -0.001669262, -0.012331606, 0.051613946, 0.062780835, 0.06123557, -0.20243123, -0.19181818, 0.032895602, 0.19760677, 0.004464939, 0.12754539, -0.27360034) * go_3(0.0, -1.0);\n    result += mat4(0.15006685, -0.083587274, -0.03215495, -0.16992462, -0.011944293, 0.058361508, -0.088097006, 0.023880545, -0.04168166, -0.06960282, -0.092672385, -0.057278465, 0.23540072, -0.1721208, -0.018213503, -0.23494521) * go_3(0.0, 0.0);\n    result += mat4(-0.124885194, 0.1905868, 0.11108704, 0.03163991, 0.11383064, 0.101223364, 0.069428995, -0.14298953, -0.07609092, 0.13704266, -0.07749446, -0.0005389336, -0.04617235, 0.18011934, 0.08350316, 0.09416366) * go_3(0.0, 1.0);\n    result += mat4(0.073356606, 0.067966126, -0.21285574, 0.0782625, -0.0034364646, -0.032581426, -0.05538558, -0.1317288, 0.14552782, -0.1132393, 0.13063973, -0.00833602, 0.0026844777, 0.028135289, -0.02536825, -0.028372496) * go_3(1.0, -1.0);\n    result += mat4(-0.318728, 0.07862527, -0.12176221, 0.35010242, -0.029198067, 0.016302662, 0.17667587, 0.12605923, 0.1556697, -0.06061443, 0.05843511, 0.10891248, 0.01267106, -0.018492714, -0.15945031, -0.050723754) * go_3(1.0, 0.0);\n    result += mat4(-0.21555941, -0.016813517, -0.084676236, -0.07545412, -0.14518794, -0.014592766, -0.2446481, 0.0530632, 0.0847341, 0.12342537, -0.028644923, 0.083479315, -0.04179012, 0.0025225023, 0.16006976, -0.026940256) * go_3(1.0, 1.0);\n    result += vec4(-0.060742114, -0.037577342, 0.055704296, 0.03134311);\n    return result;\n}\n//!DESC Anime4K-v3.2-Upscale-CNN-x2-(VL)-Conv-4x3x3x16\n//!HOOK MAIN\n//!BIND conv2d_1_tf\n//!BIND conv2d_1_tf1\n//!SAVE conv2d_2_tf\n//!WIDTH conv2d_1_tf.w\n//!HEIGHT conv2d_1_tf.h\n//!COMPONENTS 4\n//!WHEN OUTPUT.w MAIN.w / 1.200 > OUTPUT.h MAIN.h / 1.200 > *\n#define go_0(x_off, y_off) (max((conv2d_1_tf_texOff(vec2(x_off, y_off))), 0.0))\n#define go_1(x_off, y_off) (max((conv2d_1_tf1_texOff(vec2(x_off, y_off))), 0.0))\n#define go_2(x_off, y_off) (max(-(conv2d_1_tf_texOff(vec2(x_off, y_off))), 0.0))\n#define go_3(x_off, y_off) (max(-(conv2d_1_tf1_texOff(vec2(x_off, y_off))), 0.0))\nvec4 hook() {\n    vec4 result = mat4(0.13129333, -0.022117995, -0.009753253, 0.020439912, 0.044090994, -0.0916335, 0.0036765633, -0.11719207, -0.06413809, 0.04079378, -0.00085516454, -0.06306388, -0.12660664, -0.054126263, -0.005513979, 0.06364538) * go_0(-1.0, -1.0);\n    result += mat4(-0.028422508, 0.23270117, -0.28674677, -0.10820166, 0.024321957, -0.0811145, -0.07290707, -0.02125165, -0.064260505, 0.052076746, -0.009654081, 0.08363882, -0.02037171, 0.15006389, 0.121593125, -0.011237004) * go_0(-1.0, 0.0);\n    result += mat4(-0.14672333, 0.015381624, 0.1028172, -0.041823238, 0.0072677187, -0.042953942, 0.06426537, -0.0938381, -0.05990813, -0.04599802, -0.11264726, -0.027826328, -0.058160868, 0.10747306, -0.07327458, 0.07998872) * go_0(-1.0, 1.0);\n    result += mat4(-0.08702181, -0.03750975, -0.045659006, 0.04488332, 0.09102003, 0.066556975, -0.04353586, 0.08994567, -0.13561495, -0.10653702, 0.006989605, 0.028230097, 0.07177144, 0.2938447, -0.00943923, 0.022120917) * go_0(0.0, -1.0);\n    result += mat4(-0.1801194, -0.11119162, 0.1977298, -0.247902, -0.16654298, -0.07423158, 0.114130594, 0.0014401592, 0.006954727, -0.09810646, -0.051310766, 0.19487657, 0.2545855, -0.06328558, -0.04617056, 0.09444692) * go_0(0.0, 0.0);\n    result += mat4(0.011378825, 0.16044368, 0.017211074, 0.14472178, 0.032992378, -0.008925819, 0.035120245, -0.012409223, 0.074333005, 0.1178002, -0.128956, -0.13624239, -0.2791275, 0.21457297, -0.1476131, 0.04874687) * go_0(0.0, 1.0);\n    result += mat4(-0.03491764, -0.061763793, 0.05779039, 0.0054837577, -0.023937583, 0.08281698, 0.032306053, -0.014566218, 0.12738499, -0.0132100545, -0.051833414, 0.0057818824, 0.012158851, -0.20231532, -0.0043795826, 0.10285843) * go_0(1.0, -1.0);\n    result += mat4(-0.22269921, -0.15135509, -0.039143335, 0.033390045, 0.06770212, -0.14538582, -0.08011057, 0.03796648, -0.025913516, 0.13925864, 0.18309896, 0.012709204, -0.24912506, 0.3217706, 0.0394195, 0.017977878) * go_0(1.0, 0.0);\n    result += mat4(0.00080196525, 0.059145816, 0.05720508, 0.0056548906, 0.005168018, 0.09938438, 0.0200503, -0.05516137, 0.061309986, -0.019621318, -0.1541441, 0.019540716, 0.030571707, -0.09054893, 0.032851614, -0.27210873) * go_0(1.0, 1.0);\n    result += mat4(0.27061436, -0.114008114, -0.0020118617, -0.1656827, 0.09770587, 0.029897455, -0.03307522, -0.04661818, 0.033011347, 0.18498488, -0.05162084, 0.087471776, -0.24665618, -0.12538423, -0.08123797, -0.010210389) * go_1(-1.0, -1.0);\n    result += mat4(0.075188264, 0.0020608555, 0.18558815, 0.041179713, 0.11232638, 0.05507779, -0.19599183, 0.027942855, 0.06199144, 0.22141005, -0.06121163, 0.014993597, 0.24105869, -0.019737717, -0.112485714, 0.0157406) * go_1(-1.0, 0.0);\n    result += mat4(0.09425698, 0.0207658, 0.12074599, 0.009430481, 0.11889248, -0.025782838, 0.0034711843, 0.05113582, 0.012531833, -0.0018606635, -0.09137569, 0.018120576, 0.4051155, 0.02222076, -0.16001017, 0.10981527) * go_1(-1.0, 1.0);\n    result += mat4(-0.03582557, 0.014994796, -6.4688604e-05, 0.24618183, -0.11697727, 0.24388117, 0.038502026, -0.3511993, 0.101741396, -0.10748137, 0.035059888, -0.017535849, 0.09450039, 0.06541661, 0.12149035, 0.28798738) * go_1(0.0, -1.0);\n    result += mat4(-0.27143848, 0.017990451, -0.69144464, 0.037944376, -0.04551905, 0.09263134, 0.4259611, -0.14107811, -0.10641847, 0.23065196, 0.040813655, -0.07789163, 0.3087666, 0.08190437, 0.16409059, -0.06455426) * go_1(0.0, 0.0);\n    result += mat4(-0.08290655, -0.35286915, -0.18082355, -0.32229406, 0.1608227, 0.030915622, 0.09207708, 0.02655054, 0.039464593, 0.026095424, 0.052584656, 0.033881903, -0.01751319, -0.0011676399, 0.04002607, 0.1630013) * go_1(0.0, 1.0);\n    result += mat4(-0.012021132, 0.12163766, -0.07410629, -0.06879096, 0.017859738, -0.039261997, -0.028677614, -0.23610398, -0.15963873, -0.0006119958, 0.11275506, 0.0082659265, 0.05677582, 0.08676638, -0.08669759, -0.10475464) * go_1(1.0, -1.0);\n    result += mat4(0.12792721, 0.06888765, 0.31803077, 0.26002547, -0.067599155, -0.011822328, -0.2589909, -0.30024147, 0.11076704, 0.15200609, -0.018180368, -0.19146141, 0.22298847, 0.059484895, 0.034478076, 0.15610938) * go_1(1.0, 0.0);\n    result += mat4(0.0870121, -0.016420847, -0.011579898, 0.097182855, -0.120095566, -0.06843338, -0.043460473, -0.060684606, -0.027540063, -0.008499213, 0.033570655, -0.06866259, 0.01429712, -0.07424434, 0.0009466247, 0.09142678) * go_1(1.0, 1.0);\n    result += mat4(-0.03781424, 0.04587032, 0.03744051, 0.02712279, -0.051038064, 0.0669144, -0.02640278, 0.12384894, -0.0022533627, -0.010022036, 0.07536463, -0.030489929, 0.09418577, 0.155089, -0.011290433, -0.02102941) * go_2(-1.0, -1.0);\n    result += mat4(-0.0053278613, -0.07160643, 0.039028414, 0.04123311, -0.10693177, -0.1170874, 0.07230816, -0.033255517, -0.119176835, 0.0786526, -0.11880206, -0.11354601, -0.037539184, 0.14404313, 0.069760695, 0.024738638) * go_2(-1.0, 0.0);\n    result += mat4(0.03413808, -0.006487654, 0.10006853, 0.22228058, -0.13796462, -0.14042488, 0.04017443, -0.031790894, -0.06673143, 0.009888688, 0.08831443, -0.0045771743, -0.028375361, -0.04704813, 0.07128581, -0.07012518) * go_2(-1.0, 1.0);\n    result += mat4(-0.06954315, -0.23728988, -0.14192343, -0.08236467, -0.2552115, 0.04102959, -0.06355397, -0.08340241, 0.17617856, 0.20281969, -0.16249381, 0.10843737, -0.04392261, -0.08587206, 0.053069845, -0.15482199) * go_2(0.0, -1.0);\n    result += mat4(0.124981806, 0.12828638, -0.061472785, -0.20108232, -0.14905351, -0.40766275, -0.35427195, -0.13183996, 0.09307428, -0.07697028, 0.06702549, -0.22656697, 0.019868268, -0.19361132, 0.08784669, 0.20249842) * go_2(0.0, 0.0);\n    result += mat4(-0.004661343, -0.09333453, -0.24876262, -0.07906779, 0.110697776, -0.37069768, -0.042212646, -0.0046135853, -0.2254257, -0.023392014, 0.031476703, -0.045574382, -0.12675518, -0.076056994, -0.08228006, -0.040303517) * go_2(0.0, 1.0);\n    result += mat4(0.16182694, 0.0512523, 0.051189836, 0.048962783, -0.05156489, -0.17987493, -0.012037288, 0.06953726, -0.09458492, 0.1610021, -0.004063283, -0.032922342, 0.08995396, 0.1939926, -0.018710036, -0.08153231) * go_2(1.0, -1.0);\n    result += mat4(-0.064830944, 0.06121252, -0.18886387, -0.12976822, -0.031117212, 0.12219633, 0.19070715, 0.12495262, -0.11994464, -0.24687837, -0.08425294, -0.016920334, -0.13286817, -0.3260188, -0.11776061, 0.1651019) * go_2(1.0, 0.0);\n    result += mat4(-0.17652592, 0.002499805, -0.030541016, -0.01393431, 0.031418208, 0.08209422, 0.12430871, 0.4387016, -0.108871914, -0.09041422, 0.031226631, -0.1638517, 0.20756467, 0.014476537, -0.012701195, -0.03440563) * go_2(1.0, 1.0);\n    result += mat4(0.005320072, -0.0032291536, -0.017209187, 0.031944863, -0.2479921, -0.24433962, -0.13832912, 0.07835928, -0.17707248, 0.028202811, -0.19121435, 0.164587, 0.123152815, 0.0050288937, 0.084104605, -0.0380019) * go_3(-1.0, -1.0);\n    result += mat4(0.16008669, -0.018608516, -0.013778938, 0.033447385, -0.01242472, -0.070916265, 0.026909694, -0.07318777, 0.15158044, 0.12047607, -0.1709358, 0.2031767, 0.0025611701, -0.21457459, 0.2791286, 0.10159932) * go_3(-1.0, 0.0);\n    result += mat4(0.14320926, 0.020023825, -0.0484187, 0.011563084, -0.2640472, -0.013056275, 0.004234292, -0.095376395, 0.28363484, -0.0058227647, -0.0777649, 0.05238444, 0.41757923, -0.07081097, 0.012567031, -0.13029522) * go_3(-1.0, 1.0);\n    result += mat4(0.07266207, 0.042793367, -0.08212271, -0.23401663, -0.19457819, 0.4191269, -0.03095442, 0.15339781, -0.28451788, 0.09316364, 0.10231693, -0.22844811, 0.111623526, 0.120017685, 0.18777381, 0.014420896) * go_3(0.0, -1.0);\n    result += mat4(0.15037206, -0.29763284, 0.2601235, 0.0193363, 0.13686465, 0.009907918, -0.37781665, 0.04916627, 0.14114739, 0.5043813, 0.0447959, -0.029427614, 0.041768756, 0.27211213, 0.14163221, 0.086162075) * go_3(0.0, 0.0);\n    result += mat4(0.19159287, 0.21363218, 0.15053211, 0.08992885, 0.100828275, 0.09379921, 0.030783929, 0.11664482, -0.059145752, -0.19400764, -0.09351283, -0.016430443, -0.12910964, -0.067078374, 0.11760082, 0.121194765) * go_3(0.0, 1.0);\n    result += mat4(-0.055059325, 0.09299572, 0.06848913, 0.06334532, -0.1476285, 0.111801244, -0.033960916, 0.06474366, -0.04952303, 0.27885208, -0.052447475, 0.09226763, -0.15024844, -0.0033919013, 0.013498364, 0.09135676) * go_3(1.0, -1.0);\n    result += mat4(-0.017010042, -0.122343406, -0.19097193, -0.27957183, -0.18206005, 0.102321096, 0.22794476, 0.0439245, -0.23710132, -0.08070259, 0.17377135, 0.23811814, 0.17799385, 0.049567625, 0.1470908, 0.07329385) * go_3(1.0, 0.0);\n    result += mat4(0.0038071256, 0.19454515, -0.01222965, -0.07390379, -0.0532754, 0.03942833, 0.123840906, 0.023459576, -0.0658742, -0.023957543, -0.14682837, 0.1221027, -0.010986398, -0.066184506, 0.03026491, -0.0638446) * go_3(1.0, 1.0);\n    result += vec4(-0.06427697, -0.00039365015, 0.011889719, 0.060232002);\n    return result;\n}\n//!DESC Anime4K-v3.2-Upscale-CNN-x2-(VL)-Conv-4x3x3x16\n//!HOOK MAIN\n//!BIND conv2d_1_tf\n//!BIND conv2d_1_tf1\n//!SAVE conv2d_2_tf1\n//!WIDTH conv2d_1_tf.w\n//!HEIGHT conv2d_1_tf.h\n//!COMPONENTS 4\n//!WHEN OUTPUT.w MAIN.w / 1.200 > OUTPUT.h MAIN.h / 1.200 > *\n#define go_0(x_off, y_off) (max((conv2d_1_tf_texOff(vec2(x_off, y_off))), 0.0))\n#define go_1(x_off, y_off) (max((conv2d_1_tf1_texOff(vec2(x_off, y_off))), 0.0))\n#define go_2(x_off, y_off) (max(-(conv2d_1_tf_texOff(vec2(x_off, y_off))), 0.0))\n#define go_3(x_off, y_off) (max(-(conv2d_1_tf1_texOff(vec2(x_off, y_off))), 0.0))\nvec4 hook() {\n    vec4 result = mat4(-0.012110923, 0.07818654, 0.07964548, 0.11885079, -0.07694473, -0.01378252, 0.006632789, -0.12876098, 0.0069211307, 0.022278586, 0.069553085, 0.16569804, -0.11123615, 0.06125189, -0.11232848, 0.1559266) * go_0(-1.0, -1.0);\n    result += mat4(-0.3261174, -0.25586754, 0.21129315, 0.3135101, 0.1509055, 0.0044283345, 0.024674175, -0.08000473, 0.01213029, 0.09093019, 0.04942677, 0.09806723, -0.16454464, -0.14433062, -0.058094524, -0.060819894) * go_0(-1.0, 0.0);\n    result += mat4(0.023174008, 0.02858724, 0.07685972, 0.036857616, -0.10415571, 0.10241035, -0.01893166, 0.02065923, 0.058356714, 0.096426114, -0.03772327, -0.1529002, 0.13740575, -0.048291504, -0.06152548, -0.15199897) * go_0(-1.0, 1.0);\n    result += mat4(0.029300174, -0.13222043, 0.0139825605, -0.02274408, 0.062944874, 0.028447356, 0.05960515, 0.034447193, 0.03133432, -0.019283533, -0.024591971, -0.0043914663, 0.15245225, 0.006851478, -0.051783554, 0.17453748) * go_0(0.0, -1.0);\n    result += mat4(-0.09125915, 0.081739366, 0.01196335, 0.23130219, -0.22557035, -0.13537665, 0.0022028848, -0.043430023, 0.22759882, 0.07920754, -0.027986467, -0.14051494, -0.19557038, -0.03585936, -0.4258294, -0.03856216) * go_0(0.0, 0.0);\n    result += mat4(0.18511422, -0.09368415, 0.1551229, 0.04322566, -0.023400841, -0.02261204, 0.15129441, -0.007954805, -0.10739125, 0.019459398, 0.013128325, 0.018073296, 0.20886365, -0.20662378, -0.03814699, -0.09272838) * go_0(0.0, 1.0);\n    result += mat4(-0.027352437, -0.039882626, 0.12598103, -0.093930446, 0.030846786, -0.09325075, -0.009084744, -0.024584265, 0.07159868, 0.14162529, 0.19019091, 0.058855128, -0.09880401, -0.01843218, 0.14753596, -0.2449532) * go_0(1.0, -1.0);\n    result += mat4(0.06565521, 0.09150168, -0.08654865, 0.0829788, -0.07596146, -0.01815166, -0.08786775, -0.03477514, 0.20538878, -0.012766377, 0.020719538, 0.088188395, -0.034300096, 0.29972988, -0.20005241, 0.018425167) * go_0(1.0, 0.0);\n    result += mat4(0.11713916, 0.024167519, 0.05167596, -0.0027117804, -0.016994188, 0.048177514, -0.012556207, 0.010979094, 0.09098878, 0.028514355, 0.06063336, -0.06624107, 0.012754856, 0.013208708, -0.061374772, -0.0025992664) * go_0(1.0, 1.0);\n    result += mat4(-0.09053513, 0.03183455, 0.017340872, 0.12934409, -0.022161964, -0.0015361432, -0.049972344, -0.12763855, 0.12779881, -0.04697911, 0.018968226, -0.119873665, 0.05462772, -0.13919477, -0.10226718, -0.2540179) * go_1(-1.0, -1.0);\n    result += mat4(-0.29912186, -0.09291771, 0.050926663, 0.49361777, 0.21372582, 0.076717265, -0.058968987, -0.1572678, 0.3194591, -0.120582424, 0.03942037, 0.023128232, 0.24321598, 0.07046334, -0.21204855, -0.648296) * go_1(-1.0, 0.0);\n    result += mat4(0.05366883, -0.020366706, 0.020979457, -0.06893884, 0.04837168, 0.017253762, 0.008874203, -0.020785445, -0.20425391, 0.060179923, 0.046167206, 0.09863377, -0.14381303, 0.038928367, -0.06590863, -0.18408588) * go_1(-1.0, 1.0);\n    result += mat4(0.07099762, 0.2029403, -0.033945918, 0.15202214, 0.0901113, -0.27336198, -0.17693861, -0.16206753, -0.17642029, 0.09400492, -0.11165698, -0.07863893, -0.16306102, -0.056210615, 0.22173557, 0.013508989) * go_1(0.0, -1.0);\n    result += mat4(0.08541511, -0.27093616, -0.35273993, -0.48919773, 0.038383547, -0.16013749, 0.012996215, -0.03434873, 0.07024113, -0.28971404, 0.10623425, -0.0019642068, -0.062374946, 0.3291145, 0.22468035, -0.42971882) * go_1(0.0, 0.0);\n    result += mat4(0.020427933, 0.15062793, 0.08308975, -0.025095072, 0.030093266, -0.09649862, -0.03382388, -0.0016017791, 0.105402954, 0.020693144, -0.051065, 0.07704679, 0.02864139, -0.00135146, 0.03762216, 0.029277142) * go_1(0.0, 1.0);\n    result += mat4(0.01700994, 0.12214317, 0.06749582, 0.07354159, -0.093085855, -0.065021954, 0.010773045, -0.00095128635, -0.045384295, -0.072611265, -0.043900184, 0.049471326, 0.029131187, 0.03180158, -0.13313527, 0.05280797) * go_1(1.0, -1.0);\n    result += mat4(0.14751251, -0.15087761, 0.09932281, -0.099232934, -0.062390897, 0.112391844, -0.09159478, 0.15856399, 0.034708973, 0.01819943, -0.02730164, -0.13562973, -0.05687333, -0.0114601655, 0.07025971, 0.02496533) * go_1(1.0, 0.0);\n    result += mat4(-0.0117268525, -0.026162883, 0.07481553, 0.13420302, 0.029870516, 0.07405776, -0.06379041, 0.09631234, -0.07754842, 0.035888605, 0.0034764851, -0.040771756, -0.092022054, -0.034230903, -0.02281844, -0.0028173258) * go_1(1.0, 1.0);\n    result += mat4(-0.059846643, 0.016772347, -0.02287152, 0.07036337, -0.024946844, 0.09826078, -0.068491876, 0.20852126, 0.073890835, -0.058288682, 0.013093785, -0.05776076, 0.0516503, 0.052794468, 0.10837015, 0.038539834) * go_2(-1.0, -1.0);\n    result += mat4(-0.16391893, -0.008062687, -0.35022175, 0.2510062, -0.15820411, 0.048403125, 0.024878092, 0.037888516, -0.035924178, -0.068953894, -0.025386479, 0.24405715, -0.018495679, -0.051277515, 0.14754932, -0.031538483) * go_2(-1.0, 0.0);\n    result += mat4(-0.038429607, -0.047140498, -0.018157095, -0.029318782, -0.04094171, -0.11870087, 0.11214255, 0.07142628, 0.021007229, -0.005681072, 0.1662777, 0.10829575, 0.112268396, 0.03567479, -0.06738845, 0.0032037434) * go_2(-1.0, 1.0);\n    result += mat4(-0.032217573, 0.2102397, -0.20617546, -0.07920811, 0.12918773, 0.054486286, -0.13656865, 0.05806265, 0.01963165, 0.049910642, 0.15538268, 0.10724465, -0.09697837, -0.03070673, -0.0071386313, -0.11899626) * go_2(0.0, -1.0);\n    result += mat4(0.130827, 0.0051715383, -0.07212691, 0.45726067, 0.2773031, 0.2973666, 0.3951691, 0.01333662, -0.14561643, 0.04508669, 0.121690124, 0.13326228, -0.22579186, 0.058161184, 0.09281702, -0.00079749606) * go_2(0.0, 0.0);\n    result += mat4(-0.00771113, 0.09912341, -0.41895548, -0.06705759, 0.029148718, 0.052991726, 0.18665347, -0.031787418, 0.23053595, 0.09444956, 0.10691037, -0.06325714, -0.05335701, 0.1917427, -0.0065284846, 0.032622546) * go_2(0.0, 1.0);\n    result += mat4(-0.056801565, -0.019131258, -0.0939022, -0.08130343, -0.11051993, 0.0035269214, -0.047361933, -0.0543875, 0.10854369, 0.06445185, 0.016828364, -0.022595318, 0.1450623, 0.033027507, -0.020425137, 0.16169788) * go_2(1.0, -1.0);\n    result += mat4(-0.08747717, 0.07770065, 0.018155783, 0.07160794, 0.09860347, -0.04329888, -0.0043579484, -0.2014418, -0.060260013, 0.0036374568, -0.17566042, -0.2268221, 0.001273691, -0.2609373, -0.19417606, -0.04102927) * go_2(1.0, 0.0);\n    result += mat4(-0.086845055, -0.114253804, -0.13433142, -0.025941795, -0.0155711295, -0.13578776, 0.12059696, -0.08760523, -0.0057348222, 0.12164273, 0.07270617, -0.06352636, 0.08894258, 0.04140841, 0.1230304, -0.030357126) * go_2(1.0, 1.0);\n    result += mat4(0.03320213, 0.015911903, -0.06288296, -0.121976145, 0.2713457, 0.13913193, -0.092420585, 0.105714336, 0.10294281, -0.04591945, -0.11767934, 0.032249406, -0.06506192, -0.04639334, 0.08137017, -0.031746846) * go_3(-1.0, -1.0);\n    result += mat4(0.13717805, 0.0071242675, -0.077256985, -0.14974317, -0.08467893, -0.20126395, -0.06240603, 0.09554399, -0.075844854, 0.28380412, 0.046030026, 0.053188596, 0.50943077, 0.1179795, 0.32203588, -0.06712207) * go_3(-1.0, 0.0);\n    result += mat4(-0.18528835, 0.0016975187, -0.0041140947, 0.11234392, -0.34049067, -0.056880493, -0.04325441, 0.09905571, 0.10978758, 0.009608353, -0.10801905, -0.04071131, -0.09096832, -0.12350487, 0.011801418, 0.22521795) * go_3(-1.0, 1.0);\n    result += mat4(0.040283076, -0.034117915, -0.026142653, -0.06058959, 0.12511659, 0.4131219, 0.59190845, 0.39758852, 0.16032091, -0.5975032, -0.14516282, 0.115154505, 0.03874097, 0.18462797, 0.22934213, 0.05285643) * go_3(0.0, -1.0);\n    result += mat4(-0.17804009, 0.33769128, -0.14572927, -0.029545018, 0.3897, -0.055615567, 0.15232995, 0.48788264, -0.21422523, 0.03397293, 0.0337794, -0.19830915, -0.022457365, -0.35096076, 0.42616987, -0.19268763) * go_3(0.0, 0.0);\n    result += mat4(-0.13191561, -0.18337126, 0.017879983, -0.070472844, -0.09409196, -0.025770849, -0.060219247, 0.10869267, -0.17341033, -0.09199785, -0.0667796, -0.093538545, -0.21300837, 0.030474098, -0.04540468, 0.041321553) * go_3(0.0, 1.0);\n    result += mat4(-0.0998177, -0.08669185, -0.0090886615, 0.0021083376, 0.08900095, 0.5062186, 0.45537788, 0.029077586, -0.1001008, -0.0077697043, -0.0096318, 0.11706454, 0.07401959, -0.00650215, 0.06092762, 0.037442297) * go_3(1.0, -1.0);\n    result += mat4(-0.18500404, 0.0024998419, -0.11761331, -0.026825588, 0.27255726, 0.093010515, 0.3281413, -0.051473666, -0.050259475, -0.17258662, -0.23394547, 0.104795866, 0.035074063, -0.061560635, 0.05975411, -0.094255395) * go_3(1.0, 0.0);\n    result += mat4(-0.023440497, -0.021479638, 0.0036277648, 0.004972212, 0.02416659, -0.09856867, -0.03971455, -0.27094853, 0.026615402, -0.0047890246, -0.13755885, 0.16591635, -0.0016293586, 0.133207, 0.047790572, 0.029041538) * go_3(1.0, 1.0);\n    result += vec4(-0.0063728676, -0.029053684, -0.052831043, 0.006475641);\n    return result;\n}\n//!DESC Anime4K-v3.2-Upscale-CNN-x2-(VL)-Conv-4x3x3x16\n//!HOOK MAIN\n//!BIND conv2d_2_tf\n//!BIND conv2d_2_tf1\n//!SAVE conv2d_3_tf\n//!WIDTH conv2d_2_tf.w\n//!HEIGHT conv2d_2_tf.h\n//!COMPONENTS 4\n//!WHEN OUTPUT.w MAIN.w / 1.200 > OUTPUT.h MAIN.h / 1.200 > *\n#define go_0(x_off, y_off) (max((conv2d_2_tf_texOff(vec2(x_off, y_off))), 0.0))\n#define go_1(x_off, y_off) (max((conv2d_2_tf1_texOff(vec2(x_off, y_off))), 0.0))\n#define go_2(x_off, y_off) (max(-(conv2d_2_tf_texOff(vec2(x_off, y_off))), 0.0))\n#define go_3(x_off, y_off) (max(-(conv2d_2_tf1_texOff(vec2(x_off, y_off))), 0.0))\nvec4 hook() {\n    vec4 result = mat4(-0.0431447, 0.047972627, 0.09522898, 0.19048582, 0.0015511789, 0.1182684, -0.065335006, 0.061233886, -0.02451869, 0.065670215, -0.015341636, 0.06836347, 0.10215459, 0.17516296, 0.0857072, 0.072732896) * go_0(-1.0, -1.0);\n    result += mat4(0.10117189, 0.049022958, -0.016017418, -0.12119866, 0.089112304, 0.016286526, -0.025251161, 0.03239003, -0.0783818, -0.086096615, -0.13673106, -0.15934734, -0.51308054, -0.061430074, -0.16208844, 0.2227776) * go_0(-1.0, 0.0);\n    result += mat4(-0.011567444, 0.025550444, -0.018439503, -0.015003767, 0.11606929, -0.11613111, -0.040906087, -0.015202219, 0.03932618, -0.1106059, 0.03703376, 0.018548314, -0.12761284, -0.038109995, -0.23577367, 0.20272344) * go_0(-1.0, 1.0);\n    result += mat4(0.025444161, -0.075270735, 0.10999789, 0.16305386, 0.016178958, -0.074034974, 0.1177035, -0.077481024, -0.047774278, -0.029782977, 0.23137823, -0.2389453, 0.033015423, -0.10381626, -0.16437943, 0.20906886) * go_0(0.0, -1.0);\n    result += mat4(-0.098473966, 0.11013442, -0.18486807, 0.1907086, -0.17564997, -0.08509439, -0.42472756, -0.17446618, 0.3440862, 0.12719585, -0.12213955, -0.02246555, 0.18982963, 0.20809166, -0.36067408, 0.51116616) * go_0(0.0, 0.0);\n    result += mat4(-0.019805575, 0.07812505, 0.061653323, -0.08379226, 0.026396899, 0.009063019, -0.10845824, 0.0827647, 0.045301896, -0.07748021, -0.07435832, 0.14860612, -0.077515624, 0.010588131, -0.22704287, 0.26849246) * go_0(0.0, 1.0);\n    result += mat4(-0.02884339, -0.09512523, -0.038564682, 0.08862835, 0.041666254, -0.10532901, 0.040582962, -0.10063983, -0.15736029, -0.03644334, -0.005061672, 0.04302295, -0.046482194, -0.05262547, 0.05110866, 0.03204655) * go_0(1.0, -1.0);\n    result += mat4(-0.005932702, 0.033263832, 0.0044865874, -0.02328917, 0.056534443, -0.14084046, 0.022353357, 0.015087431, -0.2734596, -0.026544483, 0.06297078, 0.11277746, 0.06127936, 0.02466357, -0.04970561, 0.02098484) * go_0(1.0, 0.0);\n    result += mat4(0.013603583, 0.036264602, 0.10985147, 0.01532773, -0.09012781, 0.1132652, -0.17016481, 0.025332611, -0.077462606, 0.02990799, -0.10627784, -0.006231141, -0.089164406, -0.051507175, -0.043900985, 0.09049239) * go_0(1.0, 1.0);\n    result += mat4(-0.15391691, 0.1915742, 0.014101639, -0.022153432, 0.06291936, -0.017871676, -0.016763045, -0.14741553, -0.011252563, -0.20720159, -0.030648025, -0.0142307645, 0.010291614, -0.09243969, -0.052940153, 0.0061574522) * go_1(-1.0, -1.0);\n    result += mat4(0.032283742, 0.030768922, 0.1070225, -0.027818602, 0.10032608, 0.0061178426, -0.03561339, -0.26687133, 0.14369439, -0.11362691, -0.08980895, 0.066520914, 0.33414948, 0.006998835, 0.09193012, -0.2857383) * go_1(-1.0, 0.0);\n    result += mat4(-0.059588976, -0.02046844, -0.042585023, 0.031939838, 0.12796514, -0.06155685, 0.03540324, 0.009929082, -0.0039611827, 0.10790477, 0.049435645, -0.083034374, 0.23874004, -0.07460337, -0.020173345, -0.2006587) * go_1(-1.0, 1.0);\n    result += mat4(-0.13217632, 0.052319963, -0.026713084, -0.0051368694, -0.10380872, -0.28659084, 0.0044393227, 0.005174543, -0.05092618, -0.07092548, -0.027397033, -0.01609789, 0.13699281, -0.14706929, 0.17737861, -0.23746766) * go_1(0.0, -1.0);\n    result += mat4(0.19268502, 0.14133929, -0.1305119, -0.4034132, 0.057504695, -0.24550998, -0.081932545, 0.45489246, -0.29331785, 0.19625074, 0.063166246, 0.15158689, 0.6715147, -0.4610189, 0.08921431, 0.17761138) * go_1(0.0, 0.0);\n    result += mat4(0.044718128, -0.011809122, 0.024131307, -0.30093196, -0.05607289, 0.047759805, 0.004210022, 0.098192796, 0.030430846, 0.008207501, 0.12266905, -0.10549182, 0.11584339, -0.091016166, -0.08635591, -0.13889709) * go_1(0.0, 1.0);\n    result += mat4(-0.19226642, 0.07147627, -0.14759602, 0.4041079, 0.0744628, -0.19612685, 0.1498252, -0.06273549, 0.017959936, 0.10858338, -0.14985329, 0.062042814, -0.13240446, -0.24362786, 0.113626175, -0.15332204) * go_1(1.0, -1.0);\n    result += mat4(0.08383099, -0.13935047, -0.25981048, 0.16491203, 0.07513876, -0.28346774, 0.19722275, -0.044425573, 0.020889329, -0.22140723, 0.025403097, -0.09183192, 0.014202567, -0.18666178, 0.062913105, -0.047674105) * go_1(1.0, 0.0);\n    result += mat4(-0.1862771, 0.25878942, -0.043018065, 0.22144824, 0.016088247, 0.12113542, -0.11965952, -0.01587184, 0.07830932, -0.16069177, 0.13421321, 0.018718706, 0.09548377, 0.018543294, 0.013614677, -0.1054485) * go_1(1.0, 1.0);\n    result += mat4(-0.2121733, -0.015635416, 0.027564054, -0.085904464, 0.064805664, -0.070543915, 0.08966146, -0.06359783, 0.01131311, 0.046913184, -0.09809833, -0.092063695, -0.087217696, 0.012411829, 0.0045399712, 0.027389864) * go_2(-1.0, -1.0);\n    result += mat4(-0.19307798, 0.09449126, 0.084036835, 0.30262446, 0.011706106, 0.029800637, 0.04612629, 0.006186647, 0.11228541, 0.055147965, 0.17659879, -0.023410015, 0.19965266, -0.06684007, -0.081968054, -0.052410994) * go_2(-1.0, 0.0);\n    result += mat4(-0.058564443, 0.08252549, 0.058217794, 0.0864448, -0.25663558, 0.080260284, -0.0010294432, 0.05830051, -0.07684524, 0.1820709, 0.04438993, 0.019178499, -0.12425012, -0.04596089, -0.010032888, -0.0012803525) * go_2(-1.0, 1.0);\n    result += mat4(-0.43352658, 0.15262963, 0.25620222, 0.22428556, 0.09667152, 0.0037820593, -0.07951691, -0.11553085, 0.12982155, 0.17988266, -0.14283511, 0.074744284, 0.03604327, 0.00452661, -0.12865154, -0.020020623) * go_2(0.0, -1.0);\n    result += mat4(0.06850602, -0.18057181, 0.2093389, -0.07333886, 0.28406742, -0.048766967, 0.18114483, 0.47292945, -0.2340266, -0.06862712, 0.28263155, 0.3150323, -0.054724697, -0.16958356, 0.27928987, -0.19666018) * go_2(0.0, 0.0);\n    result += mat4(0.03281329, 0.0038649621, -0.07108877, 0.10791149, 0.15235375, -0.3083721, 0.168294, 0.10379698, 0.029038485, 0.16282903, 0.04483725, -0.018684763, 0.108186625, 0.027885616, -0.019351846, 0.1623065) * go_2(0.0, 1.0);\n    result += mat4(-0.110499054, 0.31347123, 0.030852, 0.01631416, -0.1466389, 0.080429435, -0.18689284, 0.10667815, 0.20645237, -0.18004708, -0.10570413, -0.15435064, -0.019000605, -3.126077e-06, 0.037761535, -0.015040956) * go_2(1.0, -1.0);\n    result += mat4(-0.023364332, -0.023399066, 0.2712722, 0.049637552, -0.10222765, -0.2698945, 0.20991959, 0.04921932, 0.21510898, -0.0751939, -0.19781734, -0.28162366, -0.041881047, 0.0065111094, -0.04102195, 0.0982682) * go_2(1.0, 0.0);\n    result += mat4(-0.032176614, 0.019144032, -0.08985387, 0.091637276, 0.1012352, 0.0003583357, 0.07897295, -0.09531175, -0.001155058, 0.074372366, -0.026186578, 0.07283374, 0.06052053, 0.009307753, -0.03874333, -0.06228009) * go_2(1.0, 1.0);\n    result += mat4(-0.022224072, -0.15717922, -0.1406057, -0.05941157, -0.028769474, -0.21226564, -0.036570027, 0.22266355, 0.14120889, 0.014577123, 0.10216447, 0.018429281, 0.056729726, -0.055834044, 0.058146577, -0.11999068) * go_3(-1.0, -1.0);\n    result += mat4(0.009995364, -0.020045493, -0.0057422677, 0.0643022, 0.016475432, -0.030856136, 0.042140726, 0.15077904, -0.32955253, 0.0694449, 0.17931722, 0.3439302, -0.12484157, -0.10958869, -0.15755124, -0.09755644) * go_3(-1.0, 0.0);\n    result += mat4(-0.008314924, 0.07704758, 0.043228816, -0.08110893, 0.099286236, -0.053224478, 0.22877018, -0.189486, -0.00798416, 0.018341504, 0.10734141, 0.0752633, -0.042524844, -0.086395286, 0.14299925, 0.026488977) * go_3(-1.0, 1.0);\n    result += mat4(-0.052531082, 0.19139186, 0.12205995, -0.2573172, 0.15157184, 0.0073150825, 0.089774385, 0.06604469, -0.16528498, -0.002511137, 0.14287429, -0.07819732, 0.025014274, 0.15338829, 0.0761692, -0.02803716) * go_3(0.0, -1.0);\n    result += mat4(-0.21000335, 0.15277153, 0.08546171, 0.2816124, -0.16559112, -0.11068559, 0.47053605, -0.009787771, -0.0013089112, -0.06985127, 0.44743782, 0.25142467, -0.32670796, 0.044035822, -0.12545367, -0.2996084) * go_3(0.0, 0.0);\n    result += mat4(-0.11526387, 0.15654811, 0.099616654, 0.15473685, 0.21278231, 0.046207245, 0.117993094, -0.26825273, -0.12539764, 0.14013724, 0.17357737, -0.05387817, 0.076738276, -0.13339446, 0.15005626, -0.2108176) * go_3(0.0, 1.0);\n    result += mat4(-0.0008846504, -0.05998622, -0.028892396, 0.04784136, 0.0104263965, 0.10899508, -0.073364735, 0.077516064, -0.074248806, -0.21749993, -0.26203, 0.041161157, 0.09366407, -0.026498007, 0.0122177545, 0.03892727) * go_3(1.0, -1.0);\n    result += mat4(0.04349908, 0.13671173, 0.2242545, -0.028021423, -0.03802222, 0.0052366396, -0.010709643, 0.031290106, 0.06291333, -0.024909683, -0.15439379, -0.04502091, 0.2062182, -0.5983536, -0.09670497, -0.38446042) * go_3(1.0, 0.0);\n    result += mat4(-0.008962513, 0.13044207, 0.04964221, 0.012250417, 0.012129821, 0.019985713, -0.06421885, 0.009168735, -0.044516414, 0.071368866, -0.006634213, 0.06497366, 0.08578495, -0.10586125, 0.06628038, -0.14006054) * go_3(1.0, 1.0);\n    result += vec4(0.056541316, 0.041788545, -0.036094554, -0.021763096);\n    return result;\n}\n//!DESC Anime4K-v3.2-Upscale-CNN-x2-(VL)-Conv-4x3x3x16\n//!HOOK MAIN\n//!BIND conv2d_2_tf\n//!BIND conv2d_2_tf1\n//!SAVE conv2d_3_tf1\n//!WIDTH conv2d_2_tf.w\n//!HEIGHT conv2d_2_tf.h\n//!COMPONENTS 4\n//!WHEN OUTPUT.w MAIN.w / 1.200 > OUTPUT.h MAIN.h / 1.200 > *\n#define go_0(x_off, y_off) (max((conv2d_2_tf_texOff(vec2(x_off, y_off))), 0.0))\n#define go_1(x_off, y_off) (max((conv2d_2_tf1_texOff(vec2(x_off, y_off))), 0.0))\n#define go_2(x_off, y_off) (max(-(conv2d_2_tf_texOff(vec2(x_off, y_off))), 0.0))\n#define go_3(x_off, y_off) (max(-(conv2d_2_tf1_texOff(vec2(x_off, y_off))), 0.0))\nvec4 hook() {\n    vec4 result = mat4(0.0647927, 0.053666476, -0.14723225, 0.027874574, -0.0003166473, 0.07337155, -0.061972085, -0.012667777, -0.17071614, 0.091927536, -0.051160213, 0.21336353, 0.13854574, 0.09582817, 0.032316446, 0.13838023) * go_0(-1.0, -1.0);\n    result += mat4(-0.0398984, 0.108049214, 0.093780346, -0.022015186, -0.15188989, -0.1381083, 0.2998843, 0.21623154, -0.08862326, 0.025862623, 0.06895634, 0.13529755, 0.06957801, -0.0011681129, 0.105972745, -0.04722446) * go_0(-1.0, 0.0);\n    result += mat4(-0.026321493, -0.04828038, -0.012545767, -0.005490858, -0.054038163, 0.075943105, -0.11526662, 0.022242405, -0.03543104, -0.12451852, -0.14911178, 0.013503498, 0.08773292, 0.09695139, -0.013498657, -0.27424073) * go_0(-1.0, 1.0);\n    result += mat4(0.018575635, -0.11321618, -0.07853153, 0.04104883, 0.0018416744, 0.11579002, 0.03685964, -0.031546146, -0.1755398, 0.23517849, -0.08095411, 0.031999595, -0.18542038, -0.26171613, -0.20567231, -0.05683613) * go_0(0.0, -1.0);\n    result += mat4(0.1538556, 0.21723682, 0.12131733, -0.15308167, 0.103326, -0.006956118, 0.043583486, -0.23811384, -0.103285454, 0.05543916, -0.37894246, 0.32072112, 0.22651967, 0.03516268, 0.34612176, 0.23688535) * go_0(0.0, 0.0);\n    result += mat4(0.040021293, 0.0029912095, 0.04885362, 0.061496444, 0.016926387, -0.118446946, 0.038948335, -0.0934512, -0.25194243, -0.054018084, -0.07149527, 0.017903058, 0.0845516, 0.33802906, 0.11953944, -0.081294954) * go_0(0.0, 1.0);\n    result += mat4(-0.09558082, -0.36974236, -0.07524102, 0.11131445, 0.047626104, 0.12854609, -0.10264962, -0.044669047, -0.05572307, 0.34475142, -0.16806377, -0.0037204176, 0.03400533, -0.04047774, 0.024379745, 0.09056291) * go_0(1.0, -1.0);\n    result += mat4(-0.039392482, 0.2553437, 0.11705501, 0.03219211, 0.073977776, -0.16610906, -0.032796364, -0.054669864, -0.07123178, 0.00079619256, -0.36920992, -0.029054813, 0.12830003, 0.004987549, 0.08724278, -0.029499404) * go_0(1.0, 0.0);\n    result += mat4(0.021272454, -0.063295126, 0.011779576, 0.103093, -0.011095461, 0.027948728, -0.014605259, -0.04723974, -0.05334346, -0.044831257, -0.07296399, -0.03314197, -0.01687865, -0.09261895, -0.06128567, 0.092708185) * go_0(1.0, 1.0);\n    result += mat4(0.0077418387, 0.00871427, 0.060824487, 0.1093608, -0.021077013, -0.057341542, -0.04769576, -0.08144089, 0.0212823, -0.06731425, -0.04134463, -0.0016761447, -0.03402026, 0.036424547, 0.11689576, -0.14946719) * go_1(-1.0, -1.0);\n    result += mat4(0.18536687, 0.020073935, 0.17041959, 0.024790209, 0.08397728, -0.13884324, 0.013950321, -0.055075396, -0.09317963, -0.05723721, -0.060491834, 0.0017911601, -0.109154835, 0.010338362, -0.1982491, -0.21752335) * go_1(-1.0, 0.0);\n    result += mat4(0.031852514, 0.031424347, 0.07817056, 0.07770759, 0.019805199, -0.091223724, 0.11914662, 0.1673029, -0.018734453, 0.16275099, 0.23245652, 0.36139074, -0.1396047, -0.14774057, 0.13756078, -0.123794965) * go_1(-1.0, 1.0);\n    result += mat4(-0.034937833, 0.20777488, 0.10104809, -0.035140667, 0.2536575, 0.010970045, 0.16896339, -0.081219964, -0.062478427, -0.0010431948, -0.027980985, 0.11446318, -0.127309, 0.21002083, 0.044436257, -0.16986957) * go_1(0.0, -1.0);\n    result += mat4(0.06309646, -0.042341243, 0.36642808, 0.18653205, 0.06973023, 0.06315932, -0.323688, 0.25672218, 0.042820994, 0.13792914, -0.12892757, -0.09220378, -0.18939693, 0.03862022, -0.17376114, -0.24673308) * go_1(0.0, 0.0);\n    result += mat4(-0.02130602, -0.35428852, -0.011634983, -3.9823462e-05, 0.110818714, -0.2981158, 0.060209107, 0.012538829, -0.0744833, -0.050204318, -0.12676497, -0.031484153, -0.28799182, 0.22338839, -0.070876874, -0.02102363) * go_1(0.0, 1.0);\n    result += mat4(-0.07929991, 0.014598492, 0.23034762, 0.024872296, 0.07480494, -0.17139243, -0.014421178, 0.056448363, -0.028626937, -0.022152562, 0.044871796, -0.048653606, 0.009350802, 0.019022083, -0.08554845, -0.0922645) * go_1(1.0, -1.0);\n    result += mat4(-0.027405115, 0.1831188, 0.28516722, 0.19882526, 0.27299204, -0.06910511, 0.03244419, -0.0031333128, 0.061055277, -0.114398144, 0.03729459, -0.07840815, -0.37776002, -0.24129418, -0.54815483, -0.2702045) * go_1(1.0, 0.0);\n    result += mat4(0.053723935, 0.13472083, 0.09563273, 0.19009806, -0.18722993, -0.25939655, -0.016197463, -0.067061596, 0.1647598, 0.061905228, 0.06191816, -0.018582113, -0.07218153, 0.11278394, 0.05478068, -0.104871586) * go_1(1.0, 1.0);\n    result += mat4(0.0036616288, -0.045782693, -0.226954, -0.05043515, -0.078096785, -0.036197383, 0.09269631, 0.016823346, -0.0060579977, -0.041455746, 0.09032774, -0.09217121, 0.058089796, 0.060311552, 0.033079024, 0.022586476) * go_2(-1.0, -1.0);\n    result += mat4(0.0436363, -0.079482526, 0.0027447809, 0.039558932, 0.13275702, 6.898711e-05, -0.21961488, -0.11315821, 0.0076181027, -0.025279062, -0.15829584, -0.063141204, 0.062049046, 0.13117202, -0.02435016, 0.109555416) * go_2(-1.0, 0.0);\n    result += mat4(-0.010148116, 0.056620967, -0.015910713, -0.07370375, 0.1529919, 0.005792597, 0.02771225, -0.17027487, 0.096740395, 0.063347995, 0.17823112, 0.054105148, 0.04995114, -0.28613812, 0.06369567, 0.15978208) * go_2(-1.0, 1.0);\n    result += mat4(-0.13688345, 0.16967694, -0.061759472, 0.013682004, -0.1290496, 0.07167547, -0.065592445, -0.17897636, 0.057080988, 0.035630587, 0.09140394, -0.08695068, 0.16807681, 0.014749346, 0.07875138, 0.034913708) * go_2(0.0, -1.0);\n    result += mat4(-0.098915346, -0.31459075, -0.10892429, 0.1557498, -0.19764107, -0.26881596, -0.03589311, 0.45288458, -0.34171388, 0.12675741, 0.18415868, -0.19770056, 0.29025507, -0.15812592, 0.09685835, 0.0027761247) * go_2(0.0, 0.0);\n    result += mat4(0.06425249, -0.01169722, 0.06379363, 0.053835012, -0.07356561, -0.06367294, 0.108630784, -0.14137438, 0.08536725, -0.03209748, 0.07250959, -0.014214082, 0.07170588, -0.25647813, 0.1092683, 0.18791042) * go_2(0.0, 1.0);\n    result += mat4(-0.023783233, 0.14261739, 0.102011986, -0.03633555, -0.05032627, 0.09378387, 0.11764051, 0.1353335, 0.032817088, -0.1352964, -0.00667997, -0.13388929, 0.022861317, 0.0037358075, 0.018605746, -0.0009892831) * go_2(1.0, -1.0);\n    result += mat4(0.22419162, -0.23105696, -0.09900454, -0.15831396, 0.12398773, 0.097933106, -0.13189293, 0.1330756, -0.19673057, -0.037342317, -0.13462654, -0.08974021, 0.030326528, -0.0815862, -0.118352115, 0.009187904) * go_2(1.0, 0.0);\n    result += mat4(-0.012130391, -0.06408448, 0.13710785, -0.06678414, -0.09970725, -0.14895032, -0.02366641, 0.029581001, -0.07101809, 0.09414698, 0.018300869, 0.009139046, -0.0027311493, -0.2359952, -0.011602826, -0.007582444) * go_2(1.0, 1.0);\n    result += mat4(-0.15473361, -0.06868751, -0.030721204, -0.08650113, 0.071349874, -0.08177769, 0.1611948, 0.18305337, -0.0144878505, 0.10975452, -0.026968453, -0.04909913, -0.059665974, 0.056036238, -0.11623168, -0.10584912) * go_3(-1.0, -1.0);\n    result += mat4(-0.096973225, 0.054132458, -0.010600018, 0.089397885, -0.0031138035, 0.037452973, 0.041115325, 0.1924831, 0.14759748, 0.032560788, -0.082884625, 0.0324635, -0.083511285, -0.050381303, 0.025589975, -0.0981257) * go_3(-1.0, 0.0);\n    result += mat4(-0.09183111, 0.034952193, -0.048511654, 0.020719057, 0.1863456, 0.01902738, 0.14455654, -0.008500172, 0.16385981, -0.07806569, -0.031216217, -0.17002788, -0.08882952, 0.07335293, -0.2223089, 0.01706056) * go_3(-1.0, 1.0);\n    result += mat4(-0.08361569, 0.046698716, -0.016646344, 0.09351987, 0.0054158634, -0.13641126, -0.12396605, 0.011380122, 0.040951792, -0.11222528, -0.0031548145, -0.0022303525, 0.0350846, -0.03280425, -0.09972476, -0.113325305) * go_3(0.0, -1.0);\n    result += mat4(-0.19961461, -0.27561286, -0.12783135, -0.062596925, 0.005870981, -0.24796526, 0.18717633, -0.16945636, -0.076396205, -0.08411448, 0.13751988, 0.21014418, -0.008655945, -0.09848541, -0.14536901, -0.2132181) * go_3(0.0, 0.0);\n    result += mat4(0.14118621, 0.20831147, -0.020545695, 0.008340737, 0.016840864, -0.16912372, -0.121718146, 0.15108089, -0.19803092, -0.07827729, -0.047639225, -0.12277847, 0.04974115, -0.09349339, -0.2756667, -0.19581003) * go_3(0.0, 1.0);\n    result += mat4(-0.0036992705, 0.16539848, 0.022026122, 0.07740234, -0.035687633, -0.004568715, 0.017408118, -0.09757294, -0.094941914, -0.3381112, -0.12724453, 0.025583982, -0.18571027, 0.047607586, -0.0704089, -0.055323426) * go_3(1.0, -1.0);\n    result += mat4(0.13821335, 0.028168043, 0.09990671, -0.032266147, -0.067236245, 0.11512147, -0.112986445, -0.10818019, -0.10062181, 0.21276556, 0.01681818, 0.069806606, 0.09628121, 0.06456379, 0.10394843, -0.02343886) * go_3(1.0, 0.0);\n    result += mat4(0.041937463, 0.072631165, 0.045366894, -0.0046993676, 0.03946691, 0.121010706, -0.030089365, -0.007266469, 0.0092267515, 0.14853416, -0.033248078, -0.027284347, -0.10031526, 0.15864117, -0.16782752, -0.18466589) * go_3(1.0, 1.0);\n    result += vec4(0.07722432, -0.025165567, 0.034291282, -0.09902708);\n    return result;\n}\n//!DESC Anime4K-v3.2-Upscale-CNN-x2-(VL)-Conv-4x3x3x16\n//!HOOK MAIN\n//!BIND conv2d_3_tf\n//!BIND conv2d_3_tf1\n//!SAVE conv2d_4_tf\n//!WIDTH conv2d_3_tf.w\n//!HEIGHT conv2d_3_tf.h\n//!COMPONENTS 4\n//!WHEN OUTPUT.w MAIN.w / 1.200 > OUTPUT.h MAIN.h / 1.200 > *\n#define go_0(x_off, y_off) (max((conv2d_3_tf_texOff(vec2(x_off, y_off))), 0.0))\n#define go_1(x_off, y_off) (max((conv2d_3_tf1_texOff(vec2(x_off, y_off))), 0.0))\n#define go_2(x_off, y_off) (max(-(conv2d_3_tf_texOff(vec2(x_off, y_off))), 0.0))\n#define go_3(x_off, y_off) (max(-(conv2d_3_tf1_texOff(vec2(x_off, y_off))), 0.0))\nvec4 hook() {\n    vec4 result = mat4(-0.004729794, -0.0124398535, -0.08538641, -0.058604605, 0.008671952, 0.25604513, 0.020800482, 0.24144122, -0.028920606, -0.04705229, 0.030192787, 0.0010597534, 0.017666103, 0.0041322373, 0.20027764, 0.08919112) * go_0(-1.0, -1.0);\n    result += mat4(0.0001626656, 0.05816014, -0.0060765734, 0.08811165, 0.35835367, -0.016291425, -0.56892496, 0.083845764, 0.15026698, -0.15916558, 0.08069463, -0.3931291, -0.0123534845, -0.111639686, -0.14637001, -0.08171439) * go_0(-1.0, 0.0);\n    result += mat4(-0.114976816, 0.023376396, 0.13855027, 0.07438716, -0.069991484, 0.20377779, 0.23929878, -0.040769435, 0.018832395, 0.005638609, -0.091848075, 0.027843866, 0.023744943, -0.06620523, -0.11678267, 0.0844119) * go_0(-1.0, 1.0);\n    result += mat4(0.0035854098, -0.08432094, -0.17799544, -0.10041983, 0.25605857, 0.021009786, 0.030499447, -0.09928291, 0.052178737, -0.08286175, -0.057888374, 0.024606042, 0.046342995, 0.13875343, 0.11279266, 0.19826262) * go_0(0.0, -1.0);\n    result += mat4(-0.016232021, -0.21539623, 0.0936961, 0.021143785, 0.094262615, 0.049040064, 0.40978724, 0.15347758, 0.08884813, -0.24887115, -0.14756748, -0.5020875, 0.112477, 0.1466549, -0.33418837, 0.5769466) * go_0(0.0, 0.0);\n    result += mat4(-0.16832942, -0.07354198, -0.12081261, -0.055348314, 0.39716053, 0.25583258, 0.09870877, 0.2151021, -0.025700683, -0.1801462, -0.04616654, -0.02782245, -0.054461803, -0.00042802413, -0.00163228, -0.004240747) * go_0(0.0, 1.0);\n    result += mat4(-0.05193433, -0.0018198475, -0.17647028, -0.19462106, 0.1538165, 0.054894235, 0.12183955, 0.07340974, -0.0019901982, 0.0357373, -0.07597063, -0.06681543, -0.00090057997, -0.053894397, -0.010301875, -0.16553953) * go_0(1.0, -1.0);\n    result += mat4(-0.30873474, -0.2836045, 0.057037193, -0.5016378, 0.11952749, 0.102353275, 0.2351629, -0.14635189, -0.019398788, -0.08776502, 0.021669978, -0.089918956, -0.2187901, -0.1180891, -0.049789533, -0.16109149) * go_0(1.0, 0.0);\n    result += mat4(-0.078335494, -0.08867304, 0.03349591, -0.1000293, -0.20235832, 0.22917585, -0.09905303, 0.08381748, 0.014350217, -0.14478815, -0.027479894, -0.026432173, -0.10309177, -0.09860884, -0.019177807, -0.06963025) * go_0(1.0, 1.0);\n    result += mat4(0.008169383, 0.12532842, -0.23369955, 0.077973194, 0.09076616, -0.021277165, 0.1721421, -0.26914293, -0.014729218, -0.023279984, -0.057670787, 0.003598546, -0.015225789, -0.0115396585, -0.26196182, -0.10724508) * go_1(-1.0, -1.0);\n    result += mat4(0.16542235, 0.06589374, 0.07410237, 0.26753154, -0.3356288, 0.3096256, 0.07112498, -0.0992165, 0.15020338, -0.11021673, 0.18803611, 0.12918204, 0.109007336, -0.031968266, 0.057093572, 0.035949256) * go_1(-1.0, 0.0);\n    result += mat4(0.065006174, 0.031055925, 0.0390232, -0.01678507, -0.21553491, 0.14171642, -0.19541772, -0.033691674, -0.06241631, 0.07497651, 0.024557155, 0.056778047, -0.060191352, -0.0261998, 0.07493729, -0.0699132) * go_1(-1.0, 1.0);\n    result += mat4(-0.008541382, 0.020270415, -0.027760057, -0.040962905, -0.26732433, 0.34379438, -0.23012447, 0.0051356517, -0.04059567, 0.0972959, 0.039965224, -0.14796777, -0.0016924662, -0.116963714, -0.026353523, -0.29799464) * go_1(0.0, -1.0);\n    result += mat4(0.03329303, -0.12663862, -0.0004959157, -0.11162377, 0.26238343, 0.43260252, -0.16504994, 0.10727678, -0.22505566, 0.43474057, 0.43304008, 0.05143919, 0.40494493, 0.08689636, -0.035733614, 0.25727916) * go_1(0.0, 0.0);\n    result += mat4(0.12175736, -0.014467151, -0.17461288, -0.18480565, -0.26439998, 0.307935, -0.058916792, -0.014292711, -0.0569471, 0.10751278, -0.04134206, 0.1847734, -0.07519831, -0.033909313, -0.05001451, -0.136606) * go_1(0.0, 1.0);\n    result += mat4(0.1424893, -0.026820501, 0.19645774, -0.0011315406, -0.14680974, 0.07662838, 0.21108222, 0.13260938, 0.17923595, -0.085527614, 0.08217639, 0.06579479, 0.05985784, -0.09016323, 0.11172888, 0.111903176) * go_1(1.0, -1.0);\n    result += mat4(0.19842595, 0.0093640275, 0.10433465, 0.13341904, -0.082806975, 0.22555825, -0.1315717, 0.11907785, 0.24012424, 0.47776055, 0.1835734, 0.17483878, 0.079803735, 0.01155073, -0.21146573, -0.16484722) * go_1(1.0, 0.0);\n    result += mat4(0.15064004, 0.021381427, 0.18301587, 0.21225913, 0.054995645, 0.03212186, 0.052798916, -0.048424408, 0.03609021, 0.0964704, -0.059469886, -0.05133066, -0.08157349, 0.051145166, -0.09107608, -0.1362262) * go_1(1.0, 1.0);\n    result += mat4(0.090521574, -0.014747857, -0.081675015, -0.118686825, 0.04848682, -0.033071827, 0.008534588, 0.023765508, 0.16849907, -0.21797262, -0.17049783, -0.07824179, -0.033794608, 0.052612655, 0.095820345, -0.07262317) * go_2(-1.0, -1.0);\n    result += mat4(0.22816367, -0.13772108, -0.036353834, -0.47638395, -0.0530902, 0.14089061, 0.076203234, 0.18006112, 0.121814854, -0.20750527, 0.08266107, -0.28634354, 0.14301859, -0.13458411, 0.00501663, -0.039783802) * go_2(-1.0, 0.0);\n    result += mat4(-0.103384845, -0.14389835, 0.08275834, -0.068423435, 0.22643796, -0.02966374, -0.2847584, 0.037081387, 0.02349005, -0.19353923, -0.00095957273, -0.13623689, -0.073120415, 0.03941467, 0.21864155, -0.014019576) * go_2(-1.0, 1.0);\n    result += mat4(-0.082576886, 0.17085212, 0.08971252, -0.04213377, -0.032548156, 0.022137715, 0.08399252, -0.0011743539, -0.09410863, -0.41728264, -0.20709297, -0.18933547, 0.027059928, 0.09743364, 0.2504647, -0.041173562) * go_2(0.0, -1.0);\n    result += mat4(-0.20924084, 0.291118, 0.029851688, 0.16953468, 0.02936709, 0.12213576, 0.22944322, 0.108747594, 0.0001881129, -0.27398208, -0.009702691, 0.15449248, -0.9472944, -0.26114875, -0.28161275, -0.3495961) * go_2(0.0, 0.0);\n    result += mat4(-0.12994622, -0.2758638, -0.1091727, -0.0968308, -0.14323105, 0.035175014, -0.08023811, 0.006023802, -0.031529594, -0.1486306, -0.3398172, -0.23240276, -0.29163983, 0.173475, 0.18809283, 0.22197202) * go_2(0.0, 1.0);\n    result += mat4(0.048254848, -0.083444916, -0.014334202, 0.060992356, -0.023099286, -0.09492961, 0.05592045, 0.0026059286, 0.08998117, -0.108810075, -0.053304546, 0.045926623, 0.068255246, 0.099023566, 0.01595483, 0.1336309) * go_2(1.0, -1.0);\n    result += mat4(0.21916585, 0.2837387, 0.14624594, 0.18843961, -0.06747584, 0.054924384, -0.082568415, 0.05011459, 0.014297759, -0.3884833, -0.054417178, -0.18970548, 0.088336475, -0.030646667, -0.2980552, -0.030035203) * go_2(1.0, 0.0);\n    result += mat4(-0.02748568, -0.011897529, -0.2370837, -0.016740574, -0.0282112, 0.050353892, -0.10761107, -0.00036999505, 0.037646662, -0.17742962, 0.06489219, -0.158852, -0.08016933, 0.07808515, -0.105895035, 0.079869986) * go_2(1.0, 1.0);\n    result += mat4(-0.0058994526, -0.037170693, 0.2574696, 0.06199102, -0.04497728, -0.10667442, -0.15183865, 0.0212881, -0.030842574, 0.073473394, 0.010764398, -0.00084518327, -0.03893014, -0.009649613, 0.07443129, 0.15108284) * go_3(-1.0, -1.0);\n    result += mat4(0.11325495, -0.096435815, -0.097331434, -0.049700152, -0.17231967, 0.047090057, -0.019111065, 0.104790315, -0.15004838, 0.13950798, 0.055996202, -0.070548095, 0.047154237, -0.007650949, -0.053611025, -0.012242293) * go_3(-1.0, 0.0);\n    result += mat4(0.12787002, -0.04958212, 0.053988468, 0.0017896162, 0.049493514, -0.009475431, -0.0022641935, 0.03933694, -0.005174597, 0.043754533, -0.1432976, 0.037084177, -0.04601288, -0.032077815, -0.059897035, 0.12584484) * go_3(-1.0, 1.0);\n    result += mat4(0.019409029, 0.10492923, 0.268368, 0.12597778, -0.17733063, -0.0085961, -0.27136415, -0.049664587, 0.012515404, -0.21444482, -0.39275557, -0.12297177, 0.06800057, 0.19228315, 0.06245887, 0.35772634) * go_3(0.0, -1.0);\n    result += mat4(-0.16317715, 0.2288402, -0.23235172, 0.22230752, -0.1646375, 0.13366091, 0.16681044, -0.17399235, 0.33997267, -0.3179832, -0.34756508, 0.39843196, -0.10748536, 0.322923, 0.23339489, 0.08684083) * go_3(0.0, 0.0);\n    result += mat4(0.02835275, 0.12314228, 0.24030593, 0.30856124, 0.055735108, -0.044914473, 0.0031432225, 0.07469899, 0.1778018, 0.107083894, -0.023706734, -0.15501897, 0.0943098, -0.034707237, -0.18622099, 0.05257965) * go_3(0.0, 1.0);\n    result += mat4(0.042839274, 0.12597966, 0.08979042, -0.0647561, -0.050434645, 0.049438696, -0.20008127, -0.05572608, 0.046238814, 0.12622325, -0.019017145, -0.13960391, -0.040050175, 0.14298008, -0.20270552, 0.13391526) * go_3(1.0, -1.0);\n    result += mat4(-0.0073277587, 0.10606624, -0.08940439, -0.09656414, 0.12387374, -0.0013147948, 0.23607181, -0.00037969893, 0.050353236, -0.17266603, 0.27796733, -0.09877832, 0.02711225, 0.096394345, 0.07457944, 0.21541388) * go_3(1.0, 0.0);\n    result += mat4(-0.18612787, -0.00027517386, -0.17136407, -0.06413671, 0.025629476, -0.04570916, 0.0008431566, -0.03419168, 0.08123608, 0.09465922, 0.11975521, 0.1269741, 0.08413221, 0.12125001, 0.04727287, 0.072378494) * go_3(1.0, 1.0);\n    result += vec4(0.04244928, -0.014280219, 0.017129054, -0.08807801);\n    return result;\n}\n//!DESC Anime4K-v3.2-Upscale-CNN-x2-(VL)-Conv-4x3x3x16\n//!HOOK MAIN\n//!BIND conv2d_3_tf\n//!BIND conv2d_3_tf1\n//!SAVE conv2d_4_tf1\n//!WIDTH conv2d_3_tf.w\n//!HEIGHT conv2d_3_tf.h\n//!COMPONENTS 4\n//!WHEN OUTPUT.w MAIN.w / 1.200 > OUTPUT.h MAIN.h / 1.200 > *\n#define go_0(x_off, y_off) (max((conv2d_3_tf_texOff(vec2(x_off, y_off))), 0.0))\n#define go_1(x_off, y_off) (max((conv2d_3_tf1_texOff(vec2(x_off, y_off))), 0.0))\n#define go_2(x_off, y_off) (max(-(conv2d_3_tf_texOff(vec2(x_off, y_off))), 0.0))\n#define go_3(x_off, y_off) (max(-(conv2d_3_tf1_texOff(vec2(x_off, y_off))), 0.0))\nvec4 hook() {\n    vec4 result = mat4(0.01973856, -0.05053795, 0.015545361, 0.10867395, 0.33441806, 0.14731607, 0.6793983, -0.21394718, -0.00846322, 0.09146322, -0.07427475, -0.078477465, -0.090998545, 0.133366, 0.105515696, -0.13784988) * go_0(-1.0, -1.0);\n    result += mat4(-0.05404873, 0.09784018, -0.1337389, -0.18082313, 0.13461179, -0.3816801, 0.12209786, 0.08176651, 0.10461896, -0.43315184, 0.017470734, 0.20423968, -0.03941875, -0.101959296, -0.09440259, 0.09154717) * go_0(-1.0, 0.0);\n    result += mat4(0.17229515, -0.06907825, -0.008382803, -0.16671611, -0.01576541, 0.03985307, 0.08209482, -0.11707446, -0.11793074, 0.13702396, -0.02013158, 0.07302033, -0.022301994, -0.11464677, 0.036753565, -0.093276784) * go_0(-1.0, 1.0);\n    result += mat4(-0.017650167, 0.009475923, -0.17856382, 0.15925962, 0.06434641, -0.15568036, 0.038135886, 0.18855911, -0.04427734, 0.1878215, 0.10856261, 0.0041275816, -0.12046199, 0.13610138, 0.3741596, -0.12934728) * go_0(0.0, -1.0);\n    result += mat4(-0.24631616, 0.0169485, -0.035534818, 0.37795424, -0.08546174, 0.07817259, 0.42897213, -0.47965595, -0.0146556785, -0.20510523, -0.18889453, 0.06476019, 0.1021008, -0.35398817, -0.031071864, -0.21416448) * go_0(0.0, 0.0);\n    result += mat4(0.32810766, 0.050585747, -0.17658374, -0.13881154, 0.16417882, -0.21286008, -0.106835455, -0.1722344, -0.14151084, 0.08962986, 0.057395387, -0.01623662, 0.02570415, 0.15626897, -0.12687978, 0.080729105) * go_0(0.0, 1.0);\n    result += mat4(-0.050597478, -0.018753758, -0.036346875, -0.017908493, 0.058593344, 0.008303028, 0.05254987, -0.06635018, -0.022532012, 0.029511122, 0.026682215, -0.054647952, 0.069466785, -0.08892492, 0.025351115, -0.023130694) * go_0(1.0, -1.0);\n    result += mat4(0.2412473, -0.16138165, -0.15117447, 0.11851003, -0.096868426, 0.082690425, 0.27923304, 0.11590443, 0.19363573, -0.15770023, -0.066793665, 0.011681678, 0.14037277, -0.112065665, -0.048159517, 0.009453693) * go_0(1.0, 0.0);\n    result += mat4(0.1580054, -0.0060506654, 0.05267837, -0.09178131, -0.09107123, 0.23191126, 0.21108283, -0.070422985, 0.024321035, 0.06131459, 0.066626504, 0.032481454, 0.044402298, 0.1390604, -0.14432502, 0.040869843) * go_0(1.0, 1.0);\n    result += mat4(0.10264861, 0.013504324, 0.012482852, -0.1781206, -0.12799414, -0.27026084, -0.123830505, 0.098105, -0.039127555, 0.09367889, 0.122323096, 0.1416734, 0.044763107, -0.21801683, -0.14018978, 0.17646866) * go_1(-1.0, -1.0);\n    result += mat4(0.017453065, 0.11498537, -0.10998983, -0.3116098, -0.3099762, 0.5024706, 0.051817298, 0.03170681, -0.18937826, 0.07946567, -0.11978771, -0.09523745, -0.0033551592, -0.11768945, 0.08932359, -0.06689581) * go_1(-1.0, 0.0);\n    result += mat4(0.1507582, -0.013266159, -0.073085934, -0.07252967, -0.06301927, -0.13218755, 0.12984878, -0.13678701, 0.023422396, 0.082123175, 0.006906731, -0.004018426, -0.15813835, 0.13711788, 0.016018609, 0.13443229) * go_1(-1.0, 1.0);\n    result += mat4(-0.06960673, 0.16156524, -0.1374069, -0.05803206, -0.077960715, -0.10676749, 0.26282015, 0.03521529, 0.058099385, -0.014738148, 0.0011174522, 0.24279532, -0.023991548, -0.108812414, -0.08886019, 0.20584475) * go_1(0.0, -1.0);\n    result += mat4(-0.08043308, 0.063343, 0.055290066, -0.15991378, -0.08096304, -0.23888679, 0.019161629, 0.38381267, 0.3672934, -0.119608454, -0.43623593, -0.46014485, -0.5323366, 0.1318621, 0.087373205, -0.05535459) * go_1(0.0, 0.0);\n    result += mat4(0.20640239, -0.1369444, -0.21677823, 0.08202178, 0.10515278, 0.06810837, 0.073207974, 0.23623931, 0.102422275, -0.05016664, -0.0039228587, -0.1810343, -0.2235563, -0.1246854, 0.1428113, -0.10609135) * go_1(0.0, 1.0);\n    result += mat4(-0.031941894, -0.08905056, 0.21501167, 0.11244667, -0.011811734, 0.21630247, 0.07589472, -0.040489636, -0.11824066, -0.11520391, -0.10075633, -0.035642453, 0.062144946, 0.0073282206, 0.14119269, -0.060479023) * go_1(1.0, -1.0);\n    result += mat4(-0.29382935, -0.056808118, 0.051812876, -0.061358813, -0.08344258, 0.124203674, 0.037964176, -0.01961274, -0.000951725, 0.50005037, -0.24176972, 0.06487161, -0.15469861, 0.04336187, 0.17826353, 0.040010225) * go_1(1.0, 0.0);\n    result += mat4(0.02044482, -0.0879271, -0.01053958, -0.31148303, 0.07497373, -0.11548258, -0.1666126, 0.02369657, -0.058044076, 0.010801491, -0.005933901, -0.08910467, 0.007953008, 0.03761974, -0.029501524, 0.16816042) * go_1(1.0, 1.0);\n    result += mat4(0.1779597, -0.10213089, 0.29942423, -0.016642543, -0.015537001, -0.04676146, 0.09585872, -0.0055750017, -0.014361908, -0.20667697, -0.11348746, 0.13081487, -0.10437329, 0.14328459, 0.11648822, -0.09163837) * go_2(-1.0, -1.0);\n    result += mat4(0.019033967, -0.12420627, -0.07748253, 0.43203858, -0.109799065, 0.07605535, 0.060791396, -0.24517195, -0.15674245, 0.21267459, 0.10665515, -0.073150024, -0.1358355, 0.0054066703, -0.16434059, -0.06031853) * go_2(-1.0, 0.0);\n    result += mat4(-0.18834068, 0.26840356, -0.12937617, 0.16103932, -0.0062331813, -0.13630053, -0.013911821, 0.022389365, -0.044232946, -0.056454606, 0.022426741, 0.18010215, 0.041900013, 0.03375041, -0.11376866, -0.010313381) * go_2(-1.0, 1.0);\n    result += mat4(0.12497669, -0.31161824, 0.097568035, 0.19443443, -0.05056519, -0.0031457904, 0.1055554, -0.083650924, 0.07630523, -0.34177595, -0.093093194, 0.20701368, -0.030962149, -0.054470222, -0.23853977, 0.004326528) * go_2(0.0, -1.0);\n    result += mat4(0.34370202, 0.085750066, -0.16071722, -0.54335934, -0.35595295, -0.050744478, -0.17405547, 0.008628697, -0.007086256, 0.23164117, 0.340156, 0.5475976, -0.15292351, 0.28019544, 0.038059216, 0.0044727) * go_2(0.0, 0.0);\n    result += mat4(-0.08231968, -0.0052294536, 0.07451547, 0.22278999, -0.3305531, 0.0017458396, 0.10818422, -0.21325395, -0.08807993, -0.110342845, 0.10082142, -0.051594347, 0.24192205, -0.18042035, -0.0095462985, -0.08757798) * go_2(0.0, 1.0);\n    result += mat4(0.096379586, 0.021887815, -0.05097233, -0.06797989, -0.026171045, 0.022944937, -0.015915364, 0.037667938, 0.17216732, -0.014889412, 0.07343887, 0.028236505, 0.0015047621, 0.1355103, -0.09918284, -0.07673695) * go_2(1.0, -1.0);\n    result += mat4(-0.25385055, 0.15163356, 0.0030003798, 0.18464413, 0.05611221, 0.099498056, -0.07128191, 0.042955168, 0.027493173, 0.07440157, 0.07814497, 0.096160784, 0.13571084, 0.056412842, -0.031997006, -0.16073681) * go_2(1.0, 0.0);\n    result += mat4(-0.21634746, 0.025153082, -0.064477116, 0.0005679147, -0.0029436245, 0.12794618, 0.024849026, 0.03018052, 0.11723976, 0.059955597, -0.013594654, 0.09091745, 0.04775348, 0.21260159, -0.07463213, -0.06727042) * go_2(1.0, 1.0);\n    result += mat4(-0.12166018, 0.024545137, 0.08611618, -0.17627168, 0.09042604, -0.14157623, -0.22147785, 0.09100581, 0.11078359, 0.031410985, -0.17170976, 0.09532806, -0.059569277, 0.09392676, 0.11784347, -0.21471368) * go_3(-1.0, -1.0);\n    result += mat4(0.1483187, -0.2217563, 0.12032977, 0.14932398, 0.27428308, -0.04568031, 0.12670338, 0.09586169, 0.06700745, 0.005126449, 0.0027694793, -0.033667028, 0.06447861, -0.08585174, -0.05509812, -0.11358761) * go_3(-1.0, 0.0);\n    result += mat4(-0.22750492, 0.032906335, -0.029479047, 0.11580199, -0.05812372, -0.032269973, 0.05219915, 0.041658226, 0.010897959, 0.065550454, 0.0076911976, -0.045743827, 0.11614996, -0.10393113, -0.0012606392, -0.034367524) * go_3(-1.0, 1.0);\n    result += mat4(0.09350742, 0.09561609, 0.3735968, 0.031685118, -0.042026598, 0.17006761, -0.3910107, 0.16984761, 0.25679177, 0.036610503, -0.13772772, 0.11101589, -0.1137049, 0.07211461, 0.18065079, -0.12324793) * go_3(0.0, -1.0);\n    result += mat4(-0.020749722, 0.14413361, -0.061903823, -0.21550268, 0.31306142, -0.11532895, 0.029482557, 0.03282164, -0.09800627, -0.20765196, 0.33030233, 0.075725295, 0.49252015, 0.042455837, -0.07264194, -0.10401895) * go_3(0.0, 0.0);\n    result += mat4(-0.22697076, -0.15738785, 0.09740376, -0.072098814, -0.06638972, 0.12336611, 0.0073687397, 0.048267826, 0.06717852, -0.027047804, -0.123397194, 0.17829034, 0.04215185, 0.066311836, -0.061742183, -0.046373066) * go_3(0.0, 1.0);\n    result += mat4(0.041311592, 0.2813485, 0.055084586, -0.01823069, 0.08105147, -0.087944716, -0.10135052, -0.02653456, 0.063169874, -0.1351186, 0.06722432, -0.016406318, 0.08666922, 0.0555909, 0.12086502, -0.17224412) * go_3(1.0, -1.0);\n    result += mat4(0.26026788, -0.18303715, 0.029279215, -0.12858874, 0.027197823, 0.0919464, 0.00849638, 0.10547888, -0.12952055, -0.14414985, 0.1903315, 0.05004528, -0.12657289, 0.038008716, -0.036606666, -0.054025438) * go_3(1.0, 0.0);\n    result += mat4(0.069167465, 0.2699947, -0.11137602, -0.05888806, -0.107324794, -0.07598601, 0.06042177, 0.0064530694, -0.039780665, -0.076666445, -0.00846108, -0.06165907, -0.06978219, -0.19108103, -0.040026028, -0.120319635) * go_3(1.0, 1.0);\n    result += vec4(-0.14375664, -0.0056876075, 0.052177623, 0.07152566);\n    return result;\n}\n//!DESC Anime4K-v3.2-Upscale-CNN-x2-(VL)-Conv-4x3x3x16\n//!HOOK MAIN\n//!BIND conv2d_4_tf\n//!BIND conv2d_4_tf1\n//!SAVE conv2d_5_tf\n//!WIDTH conv2d_4_tf.w\n//!HEIGHT conv2d_4_tf.h\n//!COMPONENTS 4\n//!WHEN OUTPUT.w MAIN.w / 1.200 > OUTPUT.h MAIN.h / 1.200 > *\n#define go_0(x_off, y_off) (max((conv2d_4_tf_texOff(vec2(x_off, y_off))), 0.0))\n#define go_1(x_off, y_off) (max((conv2d_4_tf1_texOff(vec2(x_off, y_off))), 0.0))\n#define go_2(x_off, y_off) (max(-(conv2d_4_tf_texOff(vec2(x_off, y_off))), 0.0))\n#define go_3(x_off, y_off) (max(-(conv2d_4_tf1_texOff(vec2(x_off, y_off))), 0.0))\nvec4 hook() {\n    vec4 result = mat4(-0.15667982, -0.31441393, 0.29112124, -0.15737213, 0.022372838, 0.10690639, -0.12019085, -0.051941186, -0.30367845, 0.02612279, 0.2372532, 0.2021648, -0.20481086, -0.003770439, 0.14981231, 0.066780254) * go_0(-1.0, -1.0);\n    result += mat4(0.03270688, -0.42270073, 0.044317324, 0.15907793, 0.14681059, -0.2934784, 0.24933252, -0.067273855, 0.07752533, -0.23194817, 0.0686707, 0.08999225, 0.121678345, -0.12916678, 0.012397381, 0.012315053) * go_0(-1.0, 0.0);\n    result += mat4(-0.10090412, -0.20792678, 0.11076032, -0.02938975, -0.1944187, -0.2003259, 0.04438032, 0.36946484, -0.019868722, -0.15830222, 0.042811528, 0.015641417, 0.113098525, 0.080257006, 0.011135628, -0.2877629) * go_0(-1.0, 1.0);\n    result += mat4(0.15482685, 0.06579119, 0.28301102, 0.23729764, 0.15990537, 0.4529694, 0.107880585, 0.10668121, -0.42430598, -0.2631025, 0.10513542, -0.036242936, -0.09827965, -0.0069260495, -0.11689201, -0.041436482) * go_0(0.0, -1.0);\n    result += mat4(0.08472191, -0.13051608, 0.047930017, 0.36831668, 0.1164478, 0.21384816, 0.22062506, 0.2094167, 0.48668453, 0.32302913, 0.36268055, -0.091801375, -0.079141125, -0.26613805, -0.16608004, 0.03810683) * go_0(0.0, 0.0);\n    result += mat4(-0.13474251, -0.04824603, 0.23303726, -0.116136365, 0.0056330245, 0.15829784, 0.0012259148, 0.12648389, 0.038680512, 0.05131116, 0.024099711, 0.4555406, 0.0035716395, 0.11633299, 0.094744846, -0.2457627) * go_0(0.0, 1.0);\n    result += mat4(-0.0576871, -0.04037522, 0.16857862, 0.0031084458, -0.027274646, -0.18154246, 0.13337846, 0.035422433, -0.0030749738, -0.17288287, 0.019983152, -0.31871706, -0.03280405, 0.06825421, -0.1563798, 0.05031885) * go_0(1.0, -1.0);\n    result += mat4(-0.066631876, 0.012560506, 0.1690693, -0.018248236, 0.0450104, 0.016296914, -0.14910112, -0.16191053, 0.5078224, -0.017615631, 0.15226597, -0.13373777, 0.20148668, 0.060258996, 0.13215344, 0.18430072) * go_0(1.0, 0.0);\n    result += mat4(0.12976126, -0.072738245, 0.053067926, 0.09752956, -0.04716214, 0.04136464, 0.014162617, -0.06621296, -0.09617736, 0.057469178, 0.01280261, -0.042976785, -0.12570308, 0.006027807, 0.031038594, 0.06569918) * go_0(1.0, 1.0);\n    result += mat4(-0.12655424, -0.41563693, -0.030971345, -0.06357555, -0.14121394, -0.15667427, 0.14398985, 0.05995984, 0.0821605, 0.12462943, 0.007492498, -0.0030187522, -0.22804567, -0.10487421, 0.13180672, -0.13978589) * go_1(-1.0, -1.0);\n    result += mat4(-0.075991526, 0.12352044, -0.17844258, 0.010614991, -0.18293494, 0.25009897, -0.080779895, 0.21548378, 0.22215544, 0.048670914, -0.057372037, 0.078176, 0.17490411, 0.004919551, 0.059619516, 0.12660357) * go_1(-1.0, 0.0);\n    result += mat4(-0.06282951, 0.10929357, 0.026720649, -0.15939257, 0.17107709, -0.04334904, -0.03047162, -0.101681694, 0.03118431, 0.19994627, 0.025729552, 0.035035726, -0.0012207883, -0.08618888, 0.061205562, 0.009940555) * go_1(-1.0, 1.0);\n    result += mat4(-0.23581573, 0.08002133, -0.15170844, 0.08872338, -0.25767094, -0.09273545, 0.18153891, 0.2544269, -0.084598936, -0.089766875, -0.14610913, 0.002247754, 0.1802837, -0.019625561, 0.30239686, -0.032793984) * go_1(0.0, -1.0);\n    result += mat4(0.5223286, 0.10347663, 0.4000593, 0.25440502, -0.07646958, -0.31940606, 0.053407036, -0.09356492, 0.2738851, 0.23945184, -0.2907089, -0.45822915, 0.13415676, 0.17187089, 0.08731114, -0.27670014) * go_1(0.0, 0.0);\n    result += mat4(0.059273496, -0.107137166, 0.12087539, 0.179237, -0.021209063, -0.02548005, 0.061256204, 0.033822674, 0.54491127, -0.2475085, 0.08055858, -0.4071213, -0.045093834, 0.07161349, 0.08219979, -0.31735933) * go_1(0.0, 1.0);\n    result += mat4(-0.29527053, 0.021469543, 0.07202354, -0.07103959, 0.03990857, 0.2490762, -0.19419849, -0.13916986, -0.05325315, 0.12922864, -0.041463424, -0.031249814, 0.073991664, -0.09723187, 0.35132217, 0.024760868) * go_1(1.0, -1.0);\n    result += mat4(0.09606787, -0.0951808, -0.0059865676, -0.052033573, -0.3118038, 0.4432636, -0.12943317, 0.09484738, 0.10621756, -0.10550469, 0.11264014, 0.1402276, -0.012679125, -0.08809835, 0.029994955, -0.15121669) * go_1(1.0, 0.0);\n    result += mat4(0.123397775, 0.048338536, -0.00975707, -0.103767075, -0.041053303, -0.07228534, 0.046792876, 0.0668788, 0.29554394, 0.012451002, 0.19568972, 0.112091154, 0.10882395, -0.0995439, 0.051324263, 0.24967718) * go_1(1.0, 1.0);\n    result += mat4(0.2699648, 0.17300771, -0.16056584, 0.1099392, 0.11674778, -0.19811755, 0.111880325, -0.06075038, -0.095849104, -0.04510651, -0.04180761, -0.0052786698, 0.11037549, -0.24115366, 0.018509468, -0.07819484) * go_2(-1.0, -1.0);\n    result += mat4(0.10981622, 0.044488225, 0.050722387, -0.3146652, -0.0013019707, -0.24084032, -0.10475088, 0.026944289, 0.1592903, 0.33087498, 0.061839584, -0.043863457, -0.06904603, -0.08635262, 0.088630445, -0.15485142) * go_2(-1.0, 0.0);\n    result += mat4(-0.06810522, 0.19927117, -0.08130387, 0.11612667, -0.015104349, -7.738651e-05, -0.06419643, -0.14813533, 0.026650215, 0.015038833, 0.08161237, 0.058321163, 0.015005185, -0.16189656, 0.024501886, 0.1927279) * go_2(-1.0, 1.0);\n    result += mat4(0.31858218, 0.11962043, -0.20560326, -0.13190113, 0.02138715, -0.057066392, -0.085771754, -0.124566585, 0.044749223, 0.13687828, 0.1195792, 0.14021616, 0.26204133, 0.05119197, -0.13980037, 0.050747477) * go_2(0.0, -1.0);\n    result += mat4(-0.21238558, -0.0734057, -0.2036023, -0.34308743, -0.29370925, 0.2393742, -0.37877437, 0.036869828, -0.17053255, -0.26900926, -0.23330869, 0.32902205, -0.4882585, 0.27430108, -0.033711653, 0.15501487) * go_2(0.0, 0.0);\n    result += mat4(0.23487025, 0.085289046, -0.14281847, 0.12543266, 0.15871634, -0.13858907, 0.14810285, -0.0239261, 0.1286852, 0.07754033, 0.01072327, -0.14313328, 0.05480442, -0.12195059, 0.11341822, 0.08224607) * go_2(0.0, 1.0);\n    result += mat4(0.19490337, 0.023521842, -0.24548791, 0.0035114093, -0.07937166, -0.07674376, 0.08365873, -0.003286068, 0.023862893, 0.009626835, 0.032829892, 0.0078141205, 0.053484406, -0.08297165, 0.09303188, 0.004273738) * go_2(1.0, -1.0);\n    result += mat4(-0.0032906602, 0.13636959, 0.027821168, 0.06270053, 0.024775786, -0.077529594, 0.03799126, 0.030000908, 0.031749167, 0.04360487, 0.004448846, -0.17835903, -0.30834544, 0.013150946, -0.13758293, -0.03296242) * go_2(1.0, 0.0);\n    result += mat4(-0.14166978, 0.034131095, 0.049779188, 0.09453289, -0.011406557, -0.07020709, -0.0031981543, -0.03443845, -0.00010218944, 0.0855161, -0.10951453, 0.042758763, 0.1718446, -0.1577923, 0.0410027, -0.04992991) * go_2(1.0, 1.0);\n    result += mat4(0.1219178, 0.105126485, -0.041097324, -0.08110963, -0.04857337, -0.11544925, -0.14572923, 0.092435546, 0.091857366, 0.15425235, -0.020324683, -0.05764375, -0.020458939, -0.10527823, -0.085554086, 0.16358297) * go_3(-1.0, -1.0);\n    result += mat4(-0.12372687, -0.009976829, 0.14252265, -0.1321053, -0.05965866, -0.1393898, -0.017603246, -0.02714342, -0.16824952, -0.23083204, -0.012299022, -0.06689838, -0.015830487, 0.21299921, -0.11637202, 0.0074968333) * go_3(-1.0, 0.0);\n    result += mat4(-0.01979935, -0.182785, -0.015397454, 0.14175794, -0.011465284, 0.11285164, -0.036115747, 0.07150463, -0.083641894, -0.10221778, -0.13871445, 0.099696055, 0.04603662, -0.06463785, -0.007984529, -0.0032940735) * go_3(-1.0, 1.0);\n    result += mat4(0.072830334, -0.057334073, 0.09086239, 0.13039105, 0.06350303, 0.17130788, -0.2181585, -0.09137403, -0.31397742, -0.019071499, -0.017274613, 0.13762084, 0.10195637, -0.021455176, 0.04011394, -0.08029658) * go_3(0.0, -1.0);\n    result += mat4(-0.26982597, -0.40265098, -0.4151411, 0.038557775, -0.095602125, 0.3503172, -0.029988842, -0.03484708, 0.095536314, -0.0030311556, 0.31589827, 0.52763534, -0.12629713, -0.24356791, 0.0059487303, 0.42298427) * go_3(0.0, 0.0);\n    result += mat4(0.054166105, 0.18827972, -0.081673265, -0.06720384, 0.09375001, 0.22173035, -0.14050071, 0.108400136, -0.15553835, -0.08716729, -0.037366748, 0.10971073, -0.02560103, -0.26702073, -0.05201882, 0.2432563) * go_3(0.0, 1.0);\n    result += mat4(0.16196893, 0.0889265, -0.09887943, -0.042956755, -0.054403376, -0.123823255, 0.045847844, 0.017027669, 0.00539936, -0.112265736, 0.050549984, -0.104931094, -0.06883012, -0.25745714, 0.11155538, -0.15363649) * go_3(1.0, -1.0);\n    result += mat4(-0.22157209, 0.18200903, -0.13290548, 0.026721261, -0.06066069, -0.18150693, 0.08768983, 0.037362453, -0.1073367, -0.070236765, -0.41223463, -0.168915, -0.15517351, -0.13949952, -0.13307643, -0.15935421) * go_3(1.0, 0.0);\n    result += mat4(-0.026589906, 0.0930502, 0.05195435, 0.06301585, -0.01107014, -0.019382332, 0.027223695, -0.004045145, -0.15238355, -0.0345132, 0.06355168, 0.0011230056, 0.16690113, 0.0017829507, -0.0023939044, -0.09471834) * go_3(1.0, 1.0);\n    result += vec4(0.024455175, 0.01669877, -0.066231176, 0.036848705);\n    return result;\n}\n//!DESC Anime4K-v3.2-Upscale-CNN-x2-(VL)-Conv-4x3x3x16\n//!HOOK MAIN\n//!BIND conv2d_4_tf\n//!BIND conv2d_4_tf1\n//!SAVE conv2d_5_tf1\n//!WIDTH conv2d_4_tf.w\n//!HEIGHT conv2d_4_tf.h\n//!COMPONENTS 4\n//!WHEN OUTPUT.w MAIN.w / 1.200 > OUTPUT.h MAIN.h / 1.200 > *\n#define go_0(x_off, y_off) (max((conv2d_4_tf_texOff(vec2(x_off, y_off))), 0.0))\n#define go_1(x_off, y_off) (max((conv2d_4_tf1_texOff(vec2(x_off, y_off))), 0.0))\n#define go_2(x_off, y_off) (max(-(conv2d_4_tf_texOff(vec2(x_off, y_off))), 0.0))\n#define go_3(x_off, y_off) (max(-(conv2d_4_tf1_texOff(vec2(x_off, y_off))), 0.0))\nvec4 hook() {\n    vec4 result = mat4(0.01763509, -0.17156707, -0.06841296, -0.026132878, -0.10600523, 0.11245994, 0.121395074, -0.09331501, 0.12764473, 0.0428028, -0.11837395, 0.2092563, -0.04357652, -0.0490096, 0.024701532, 0.10518723) * go_0(-1.0, -1.0);\n    result += mat4(-0.17130826, -0.31987694, -0.07639005, 0.21362033, 0.058639023, 0.066175915, -0.25344703, -0.07923442, -0.14766373, 0.040518284, -0.031103026, -0.040075514, -0.051108997, -0.28214613, -0.18504949, 0.27544948) * go_0(-1.0, 0.0);\n    result += mat4(0.030991005, -0.011353306, 0.15237464, 0.15458584, 0.1250524, 0.19959912, 0.14049476, 0.38410887, 0.07378578, -0.017728366, 0.0963528, -0.043756213, -0.039577194, -0.11800575, -0.08392266, -0.07599512) * go_0(-1.0, 1.0);\n    result += mat4(0.022089608, -0.027317125, 0.051330008, -0.0075439885, 0.021650828, -0.0009390209, -0.12043464, 0.049332134, -0.055557396, -0.053297505, -0.0918705, -0.13089466, -0.10994107, 0.072746456, 0.11496739, -0.05225977) * go_0(0.0, -1.0);\n    result += mat4(0.29730305, 0.26317745, 0.052159555, -0.32006654, 0.48288685, -0.049926184, -0.08091092, -0.13825637, -0.1485706, -0.288657, -0.41443697, 0.06856032, -0.23809211, -0.12953928, 0.4783034, -0.47557938) * go_0(0.0, 0.0);\n    result += mat4(0.026139118, -0.23031352, 0.04861487, 0.033556074, 0.2702056, 0.22802536, -0.15385233, 0.1664119, 0.18749923, 0.36927548, -0.011473684, -0.11771165, -0.16859052, -0.4513202, 0.12863952, 0.02482837) * go_0(0.0, 1.0);\n    result += mat4(0.0073229345, -0.061915245, 0.06710329, 0.0062416573, -0.00555983, 0.14592186, 0.11201052, -0.123630054, 0.32611257, -0.11279885, -0.059449438, 0.2891043, -0.10519016, 0.040108994, -0.012468261, 0.02083298) * go_0(1.0, -1.0);\n    result += mat4(-0.057483062, 0.08454755, -0.15529329, -0.12572923, 0.2600099, -0.02319978, -0.04037675, 0.11496361, 0.07728194, -0.12908956, -0.025529336, 0.112581626, 0.02971823, 0.11659056, -0.01298622, 0.017061908) * go_0(1.0, 0.0);\n    result += mat4(0.22417091, -0.00222947, 0.04980858, 0.12260437, -0.025507605, 0.042577885, 0.120813504, -0.048522256, -0.038494784, -0.0072195013, -0.23012944, -0.020850847, -0.078296244, -0.014830018, 0.19759563, -0.10000253) * go_0(1.0, 1.0);\n    result += mat4(-0.032090195, 0.023757193, -0.08989734, 0.14419042, 0.0112194475, -0.093776144, -0.020197887, 0.29295877, 0.06872183, 0.09511462, -0.03245769, -0.06504889, 0.05132126, 0.00399527, 0.075911656, 0.250893) * go_1(-1.0, -1.0);\n    result += mat4(-0.3418496, 0.25525784, 0.0018161442, 0.028484365, -0.17573346, -0.12457501, 0.18466166, 0.20209278, 0.10282706, 0.16353399, 0.025052028, -0.059714165, -0.055806916, -0.28651386, 0.112798095, 0.11624314) * go_1(-1.0, 0.0);\n    result += mat4(-0.018793896, 0.07500149, -0.01728254, -0.1726998, -0.13333, 0.09590344, -0.036537904, -0.11522523, 0.19445558, 0.22680458, 0.12061006, -0.06225618, 0.1127748, 0.28380096, -0.07099846, -0.007440302) * go_1(-1.0, 1.0);\n    result += mat4(-0.43887648, -0.10018577, -0.29267642, 0.12149727, -0.14333835, 0.04161915, 0.19442867, 0.16506511, 0.09655387, -0.0014398015, 0.13189743, -0.14068556, 0.049408, 0.0829072, 0.2950336, 0.36965907) * go_1(0.0, -1.0);\n    result += mat4(0.41486958, -0.023498302, -0.37900022, -0.31752598, 0.13758768, -0.18782206, -0.31358528, 0.3330786, -0.4039293, -0.06539036, 0.032599606, 0.10663507, -0.26369813, -0.17365438, 0.20723309, 0.1801556) * go_1(0.0, 0.0);\n    result += mat4(0.004117444, -0.14894462, 0.14915143, -0.047375835, -0.2609916, -0.10172324, -0.14925237, -0.33830285, 0.12131607, -0.18156646, -0.42382464, -0.052582145, 0.2329045, -0.4576963, 0.13756892, 0.055571318) * go_1(0.0, 1.0);\n    result += mat4(-0.31689477, 0.017058033, -0.01904924, -0.016893756, -0.011479519, 0.07316262, -0.07086077, 0.08923511, -0.08190091, -0.025866933, -0.06909204, -0.028601022, 0.023224542, 0.03082087, 0.2230426, -0.16713654) * go_1(1.0, -1.0);\n    result += mat4(0.13457374, 0.110913865, -0.1130815, -0.031438913, -0.55201167, 0.04831016, 0.25107765, -0.014003224, 0.19532952, 0.02062346, 0.04839241, 0.088673405, 0.30325848, -0.20222804, -0.085780576, 0.22512968) * go_1(1.0, 0.0);\n    result += mat4(0.076354, 0.021940092, -0.16170324, 0.0025543426, -0.0032400405, -0.0046705627, 0.06241069, -0.031247333, 0.098353796, 0.03723474, 0.22971998, -0.017877292, 0.119858086, 0.008041448, 0.2140585, 0.10343376) * go_1(1.0, 1.0);\n    result += mat4(0.08627595, 0.04532834, 0.027579082, -0.16222088, 0.15583228, -0.14371829, -0.07243855, -0.111895435, -0.14438897, -0.10250594, 0.0034202964, -0.066547595, -0.034390844, -0.021545287, 0.014540157, -0.10215731) * go_2(-1.0, -1.0);\n    result += mat4(0.19720152, 0.21534947, 0.1130938, -0.011730973, 0.013247983, -0.10344174, -0.1906514, -0.015767017, -0.020093633, -0.26487067, -0.005960781, -0.057149183, 0.030110173, 0.047692046, -0.19308545, -0.25292158) * go_2(-1.0, 0.0);\n    result += mat4(0.039498243, 0.053682897, -0.01844695, -0.017540915, 0.039454967, -0.27696076, 0.09503274, -0.038958035, 0.17321438, -0.036311295, 0.03123055, 0.02310311, 0.040591653, 0.0054627894, -0.03520426, -0.026101988) * go_2(-1.0, 1.0);\n    result += mat4(0.055991564, 0.06512919, -0.12532505, 0.024075158, -0.04926237, -0.11701171, 0.026792146, 0.013033238, -0.052847516, -0.01550091, -0.008442071, -0.077945165, -0.033220004, -0.13678443, -0.07040586, 0.121846326) * go_2(0.0, -1.0);\n    result += mat4(-0.19537796, -0.016634773, 0.10707109, -0.024361614, -0.16002733, -0.44066608, 0.16488662, 0.013152995, 0.22407806, 0.12854017, 0.19028598, -0.08379244, -0.05594235, -0.15909895, 0.511962, 0.39027596) * go_2(0.0, 0.0);\n    result += mat4(-0.032652248, 0.06004893, 0.011166194, 0.102761306, -0.035113614, -0.29961765, -0.013817978, 0.20938557, 0.08488225, -0.1118558, -0.0375328, -0.035511103, 0.0046933405, 0.20203683, -0.13552529, -0.12685429) * go_2(0.0, 1.0);\n    result += mat4(0.03054923, 0.08224908, -0.059128158, -0.02583655, -0.02133876, 0.0048713544, 0.10848829, 0.06324404, 0.028332822, -0.011002306, -0.027557913, -0.06072362, 0.1019048, -0.02587316, 0.08563405, -0.08119947) * go_2(1.0, -1.0);\n    result += mat4(-0.10568117, 0.1075248, 0.19379964, -0.14337265, 0.019374132, -0.0907804, -0.13827625, -0.03628561, 0.014735499, -0.026882607, -0.25948793, 0.034926686, -0.05988073, -0.22735636, 0.053511668, 0.04765336) * go_2(1.0, 0.0);\n    result += mat4(-0.029848114, 0.09183966, 0.084713496, 0.09422864, 0.069713995, -0.10584984, -0.020899031, 0.059645247, -0.075805016, -0.01828552, 0.06689195, -0.13804196, -0.023465823, -0.034038994, -0.12946706, 0.058709413) * go_2(1.0, 1.0);\n    result += mat4(0.061918218, 0.038984764, 0.013660938, -0.19340219, -0.014949839, 0.12946278, 0.12725051, 0.13429146, 0.05993008, -0.015394284, 0.011232483, 0.0344157, 0.022161875, -0.023923954, 0.061736204, 0.025963215) * go_3(-1.0, -1.0);\n    result += mat4(0.048136763, 0.03162042, -0.01967249, 0.06374493, 0.034645267, 0.22403605, 0.036197048, -0.06903216, -0.1024706, -0.0005459356, 0.049185563, 0.16309108, 0.07394778, 0.10351343, 0.28430694, -0.13531347) * go_3(-1.0, 0.0);\n    result += mat4(-0.14705071, -0.09458433, 0.03063114, 0.07901115, -0.11911086, -0.06428132, -0.013549552, -0.041342866, -0.20770676, -0.15104479, 0.054365363, -0.11652907, 0.05639815, 0.070518605, 0.0017846811, -0.00056205114) * go_3(-1.0, 1.0);\n    result += mat4(0.27148908, 0.07358356, 0.13644488, -0.13824654, 0.0112991175, -0.021521023, -0.10197379, 0.007816017, -0.13314332, 0.12318473, -0.043214846, -0.15759036, -0.19744353, -0.10267182, -0.28249928, 0.11233295) * go_3(0.0, -1.0);\n    result += mat4(-0.096474804, 0.17893109, 0.014679829, -0.21218887, -0.24170275, 0.10603527, 0.05375366, -0.059315052, 0.17087384, 0.13633691, -0.37958893, 0.43264794, 0.17829923, 0.06485103, -0.37551817, -0.22082718) * go_3(0.0, 0.0);\n    result += mat4(-0.30536333, -0.033212308, -0.25232, 0.11730442, -0.11176368, 0.26223183, -0.049025323, -0.01375941, -0.29028055, 0.16842811, -0.035684332, -0.4180911, -0.1611732, 0.07683385, -0.14263596, 0.17508087) * go_3(0.0, 1.0);\n    result += mat4(0.23580009, 0.025621435, -0.15757325, 0.008123166, -0.021905439, -0.02162503, -0.059497356, -0.01636353, 0.047654126, -0.084423855, -0.033733923, 0.0127116265, -0.059593942, -0.053935718, -0.050729543, 0.013887048) * go_3(1.0, -1.0);\n    result += mat4(-0.19232626, 0.07915767, -0.05909752, 0.007695347, 0.058876406, 0.057521783, -0.080253534, 0.2011056, -0.27965516, -0.08033169, -0.13025513, 0.12854645, 0.053400308, -0.18445957, -0.18463044, 0.27920377) * go_3(1.0, 0.0);\n    result += mat4(-0.061806213, -0.020037206, 0.003183183, -0.029844081, -0.039553937, 0.028905323, -0.11367984, -0.097321615, -0.10112643, 0.0039709485, -0.06020118, -0.23871279, -0.077974856, 0.05806996, -0.21440302, 0.11898043) * go_3(1.0, 1.0);\n    result += vec4(-0.023832673, 0.03702965, -0.04749135, -0.10982549);\n    return result;\n}\n//!DESC Anime4K-v3.2-Upscale-CNN-x2-(VL)-Conv-4x3x3x16\n//!HOOK MAIN\n//!BIND conv2d_5_tf\n//!BIND conv2d_5_tf1\n//!SAVE conv2d_6_tf\n//!WIDTH conv2d_5_tf.w\n//!HEIGHT conv2d_5_tf.h\n//!COMPONENTS 4\n//!WHEN OUTPUT.w MAIN.w / 1.200 > OUTPUT.h MAIN.h / 1.200 > *\n#define go_0(x_off, y_off) (max((conv2d_5_tf_texOff(vec2(x_off, y_off))), 0.0))\n#define go_1(x_off, y_off) (max((conv2d_5_tf1_texOff(vec2(x_off, y_off))), 0.0))\n#define go_2(x_off, y_off) (max(-(conv2d_5_tf_texOff(vec2(x_off, y_off))), 0.0))\n#define go_3(x_off, y_off) (max(-(conv2d_5_tf1_texOff(vec2(x_off, y_off))), 0.0))\nvec4 hook() {\n    vec4 result = mat4(0.030931145, 0.013683292, -0.0650242, -0.028732346, 0.120067924, -0.029404473, 0.0038229884, -0.14631765, 0.041900825, -0.076596744, -0.11096378, -0.27100095, 0.0052598766, -0.05929686, -0.06816563, -0.086864315) * go_0(-1.0, -1.0);\n    result += mat4(-0.043620087, -0.16360405, 0.006527374, 0.15706524, 0.08338088, -0.19027525, 0.22595987, -0.054963548, 0.01825031, -0.03149212, 0.025471251, 0.06429379, -0.011633275, -0.079389006, -0.0030728737, 0.17345747) * go_0(-1.0, 0.0);\n    result += mat4(-0.011275288, -0.10668036, 0.05718997, 0.010336089, 0.33393976, -0.2029354, 0.075444475, -0.092244044, 0.07605498, 0.20125951, 0.10493973, -0.12306946, 0.03658231, 0.08233366, -0.12205888, -0.116969004) * go_0(-1.0, 1.0);\n    result += mat4(-0.0070305974, 0.105127215, 0.006041873, 0.26743913, 0.028119443, 0.14823505, -0.28344348, 0.12362866, -0.1215781, 0.08104382, 0.102011785, 0.085380934, 0.061244503, -0.06230063, -0.05353345, 0.1166729) * go_0(0.0, -1.0);\n    result += mat4(0.08945733, 0.4101902, -0.06404005, 0.040728435, 0.13076581, -0.20805469, -0.10897316, -0.14924604, 0.10090762, 0.015475414, 0.26346552, 0.12096677, -0.20199244, 0.2780031, 0.18515368, 0.35105625) * go_0(0.0, 0.0);\n    result += mat4(0.07463155, 0.26932517, -0.06768551, 0.10470878, -0.1423996, 0.013550665, -0.06167201, -0.1022994, -0.3107166, -0.15609552, 0.1695213, -0.1277181, 0.12582655, -0.1596128, 0.015612055, -0.19826376) * go_0(0.0, 1.0);\n    result += mat4(0.011745468, 0.006471601, 0.008110513, 0.025831396, 0.1272883, -0.221959, 0.11993834, -0.007903633, 0.009993582, -0.10170755, 0.026594637, -0.027883623, 0.030666083, -0.036415886, 0.007469573, 0.0674783) * go_0(1.0, -1.0);\n    result += mat4(-0.022760388, -0.10911659, -0.012589904, -0.046462692, 0.36987287, 0.71668935, -0.04466556, 0.12082762, 0.0026539841, 0.07070946, -0.00020439121, -0.13925348, 0.08672072, 0.20075354, -0.066352285, 0.14655356) * go_0(1.0, 0.0);\n    result += mat4(-0.081081845, -0.21956222, 0.06781787, -0.106362104, -0.03016425, -0.010460211, -0.009725996, -0.009805538, 0.07037355, 0.19254607, 0.038890257, 0.29580075, -0.10355764, 0.12613009, 0.02485986, -0.031927988) * go_0(1.0, 1.0);\n    result += mat4(-0.13882205, 0.21770848, 0.015392157, 0.010310204, 0.008225721, 0.07457836, 0.09984027, -0.25452816, 0.2193511, -0.22262146, -0.12950355, 0.026151875, 0.022114651, -0.030566849, 0.034688126, 0.03047327) * go_1(-1.0, -1.0);\n    result += mat4(0.0363441, 0.19290726, -0.1143055, 0.30871987, -0.05780708, 0.082128406, -0.115280904, 0.07636388, 0.48947453, -0.29715258, 0.146737, -0.3275992, -0.055972476, -0.09991753, 0.17435446, 0.10917291) * go_1(-1.0, 0.0);\n    result += mat4(0.026389305, 0.054523308, -0.028950177, 0.06913328, -0.18626037, 0.08829993, 0.10407121, 0.001246911, 0.103938825, -0.3117343, -0.045564886, 0.07316613, 0.0027089121, 0.099437356, -0.046500806, -0.0927284) * go_1(-1.0, 1.0);\n    result += mat4(0.051037624, -0.2068234, 0.061572235, -0.3345198, 0.16960172, -0.30289862, -0.002583443, 0.39312238, 0.08246557, 0.16374862, -0.31902805, -0.13205275, -0.032050006, 0.01670186, 0.13852347, 0.120012194) * go_1(0.0, -1.0);\n    result += mat4(-0.67096996, -0.06274476, 0.18575665, 0.80282855, 0.23201196, -0.0054729837, 0.050396994, -0.42014772, 0.34904522, 0.26281372, 0.24697208, 0.55475426, 0.49850988, -0.06581312, -0.0068906257, -0.15741143) * go_1(0.0, 0.0);\n    result += mat4(-0.04252036, -0.28224963, 0.009723064, 0.116357096, 0.2992567, -0.26702902, -0.05648925, 0.12729199, -0.37574205, 0.54211813, -0.25248805, -0.13023548, 0.18903324, -0.5182459, 0.0141203115, -0.19444294) * go_1(0.0, 1.0);\n    result += mat4(-0.0017735233, -0.010132458, -0.040924776, -0.13767008, 0.20757031, -0.06509882, -0.09756446, 0.018974079, 0.090851985, -0.010158765, -0.03999607, -0.12055641, 0.03629025, -0.018645551, -0.05506811, -0.014202848) * go_1(1.0, -1.0);\n    result += mat4(0.16203491, 0.011118734, -0.18486023, -0.024290733, -0.3673846, -0.20295864, 0.23055002, -0.1555852, -0.02706522, 0.03262891, 0.008724611, -0.03760652, -0.20946771, -0.01951837, 0.16955496, 0.11690098) * go_1(1.0, 0.0);\n    result += mat4(0.0783421, 0.22656651, -0.15715368, -0.024174158, 0.020260733, 0.032390315, -0.029133298, 0.086601086, 0.13871798, -0.12525433, 0.16097449, 0.058946393, 0.029865682, 0.08508385, 0.040569812, -0.09402932) * go_1(1.0, 1.0);\n    result += mat4(-0.05063873, 0.11269313, -0.057484943, -0.13579641, 0.047973365, -0.07103839, -0.07838756, -0.0028928046, -0.019466015, 0.018428024, 0.010016324, -0.057396665, -0.19495595, 0.034307264, -0.022888038, 0.08112259) * go_2(-1.0, -1.0);\n    result += mat4(-0.09790086, 0.10613111, 0.06611674, 0.19356097, -0.00073371036, -0.019078335, 0.076719105, -0.016212497, -0.3283475, -0.07547389, -0.08140701, 0.3185625, -0.25060275, 0.16820994, -0.123497784, 0.43272668) * go_2(-1.0, 0.0);\n    result += mat4(-0.06365342, 0.11186735, -0.17493224, -0.04207358, 0.0003117533, 0.034089327, -3.067692e-05, -0.03422754, 0.16267666, 0.054771993, 0.048384454, -0.041866794, 0.0036008756, 0.0021496525, 0.20258942, -0.06297619) * go_2(-1.0, 1.0);\n    result += mat4(0.03578836, 0.08763908, -0.22370125, -0.32465744, 0.019142643, 0.011316954, 0.17920344, 0.031633645, 0.03766343, -0.116487674, -0.05281752, -0.018965483, 0.049297336, -0.34511214, 0.42598158, 0.051361635) * go_2(0.0, -1.0);\n    result += mat4(0.26638633, -0.33628765, 0.04437907, 0.09616201, -0.020049393, 0.2560829, -0.027108455, 0.255752, 0.3666511, 0.052277412, -0.46667686, 0.48482272, 0.51302284, -0.06941614, -0.17967525, -0.07889891) * go_2(0.0, 0.0);\n    result += mat4(0.18503937, 0.088710256, 0.2083147, -0.20758459, -0.036416974, 0.018303726, 0.03729963, -0.035969947, -0.2685231, -0.42169708, -0.039593916, -0.02642618, 0.29050872, -0.25723743, -0.111259766, 0.15001127) * go_2(0.0, 1.0);\n    result += mat4(-0.026473878, -0.07241443, 0.022400148, -0.03214132, 0.0859297, -0.0036677981, -0.07039137, 0.03703108, 0.042322673, -0.01222808, -0.08151938, 0.033109214, -0.048737407, 0.25929528, -0.40535828, -0.123594694) * go_2(1.0, -1.0);\n    result += mat4(0.10233285, 0.22455986, -0.13368733, 0.033236265, -0.052114893, -0.11709317, 0.009709581, 0.19201641, -0.02973698, 0.032114245, -0.09771862, 0.085680574, 0.15827927, -0.15042172, 0.21833214, -0.13262676) * go_2(1.0, 0.0);\n    result += mat4(-0.08460587, -0.09473209, 0.019323658, -0.057233352, 0.0019434267, -0.14437936, 0.034232683, 0.0030602294, -0.023598112, 0.10692026, -0.09960999, 0.005887181, 0.014738836, -0.32473162, -0.10886747, -0.08365826) * go_2(1.0, 1.0);\n    result += mat4(0.10900178, 0.00080280803, -0.14009437, -0.053074867, -0.07811151, -0.03456029, -0.104943685, 0.016918905, -0.11335709, 0.079421654, 0.13481963, 0.037818357, -0.027339859, 0.05856774, -0.044562265, 0.03908084) * go_3(-1.0, -1.0);\n    result += mat4(0.07628258, -0.23815769, 0.2840278, -0.3541637, -0.044292126, -0.09310441, -0.1335055, -0.031899665, -0.11981227, 0.24012394, -0.041896038, -0.10168982, 0.20248915, -0.10036763, -0.044115108, 0.08520525) * go_3(-1.0, 0.0);\n    result += mat4(0.07234102, -0.119480744, -0.01401321, -0.025182616, -0.031284854, -0.050089385, 0.014808948, 0.038662236, -0.18539418, 0.017342187, 0.023812262, 0.13428104, 0.020824855, -0.07433546, 0.054307282, 0.08511016) * go_3(-1.0, 1.0);\n    result += mat4(-0.11046813, -0.04663274, 0.33497185, 0.023273284, -0.24681108, 0.116665915, 0.12045893, 0.13306482, -0.039098527, 0.04747061, 0.042796664, 0.053514794, 0.011861975, -0.048702, 0.008408589, -0.09497112) * go_3(0.0, -1.0);\n    result += mat4(0.34634927, 0.37973458, -0.79267627, -0.7362719, 0.35489878, -0.07635863, 0.24082923, -0.27480397, -0.3236968, -0.25523046, 0.05118527, -0.040529836, -0.6000509, 0.39020586, 0.27632973, 0.5141453) * go_3(0.0, 0.0);\n    result += mat4(0.16761221, -0.033125393, 0.00561569, 0.083019435, -0.101278506, 0.07810264, 0.12060661, 0.16048536, 0.14257826, -0.15996903, 0.018831912, -0.094429865, -0.22227801, 0.426937, -0.054677445, 0.05067348) * go_3(0.0, 1.0);\n    result += mat4(0.02233958, 0.02608942, -0.045318656, 0.06509929, 0.035911568, 0.025316885, 0.0840986, 0.08326237, 0.048455603, -0.13630742, 0.07230253, -0.047261715, -0.092630014, 0.04786565, 0.10354939, -0.07094341) * go_3(1.0, -1.0);\n    result += mat4(-0.1463382, -0.14900577, 0.2835977, -0.106733374, -0.11554754, -0.168429, -0.1411373, -0.20654152, -0.06388508, 0.039648015, 0.08543832, -0.13253337, 0.017264463, -0.06346233, -0.10823598, 0.067361064) * go_3(1.0, 0.0);\n    result += mat4(0.04419582, 0.039152585, 0.06222691, 0.05757103, 0.012084537, 0.051425997, -0.061130576, 0.16752882, 0.07497411, 0.13495837, -0.15585983, -0.02050144, -0.08555421, -0.09147339, 0.025115604, 0.05948922) * go_3(1.0, 1.0);\n    result += vec4(0.00590038, 0.03082865, 0.002111702, -0.03330112);\n    return result;\n}\n//!DESC Anime4K-v3.2-Upscale-CNN-x2-(VL)-Conv-4x3x3x16\n//!HOOK MAIN\n//!BIND conv2d_5_tf\n//!BIND conv2d_5_tf1\n//!SAVE conv2d_6_tf1\n//!WIDTH conv2d_5_tf.w\n//!HEIGHT conv2d_5_tf.h\n//!COMPONENTS 4\n//!WHEN OUTPUT.w MAIN.w / 1.200 > OUTPUT.h MAIN.h / 1.200 > *\n#define go_0(x_off, y_off) (max((conv2d_5_tf_texOff(vec2(x_off, y_off))), 0.0))\n#define go_1(x_off, y_off) (max((conv2d_5_tf1_texOff(vec2(x_off, y_off))), 0.0))\n#define go_2(x_off, y_off) (max(-(conv2d_5_tf_texOff(vec2(x_off, y_off))), 0.0))\n#define go_3(x_off, y_off) (max(-(conv2d_5_tf1_texOff(vec2(x_off, y_off))), 0.0))\nvec4 hook() {\n    vec4 result = mat4(0.009029573, 0.029218858, 0.029705316, -0.019268971, -0.0023235187, -0.072589695, 0.1424836, 0.09049359, 0.04342995, 0.18134294, 0.018145641, 0.14789368, 0.050923645, 0.06524081, 0.036812488, 0.11108108) * go_0(-1.0, -1.0);\n    result += mat4(-0.026506428, 0.016968496, 0.015961196, 0.010030791, -0.3141888, -0.06769598, -0.23920257, -0.031002127, -0.07351358, -0.19290134, -0.24282931, -0.18831016, -0.0928966, 0.075177215, -0.19699521, -0.05810917) * go_0(-1.0, 0.0);\n    result += mat4(-0.017991852, -0.079427645, 0.035970494, -0.017095685, -0.27197137, -0.20046075, 0.2616644, 0.021876303, -0.077394076, -0.04978692, 0.20363241, -0.013741705, -0.032103598, 0.14403099, 0.01442474, 0.048115995) * go_0(-1.0, 1.0);\n    result += mat4(-0.16939245, -0.001777, 0.026244136, -0.14122388, -0.056853324, 0.54357284, -0.19769607, -0.03187079, 0.04559263, -0.16048127, 0.12830622, 0.1442168, 0.006611398, -0.01618195, 0.012860053, -0.16539487) * go_0(0.0, -1.0);\n    result += mat4(0.13116026, -0.006161343, 0.7209969, 0.18338475, 0.3099777, 0.6500026, 0.3883795, -0.021434233, 0.31667513, 0.008917659, 0.14124091, -0.22335114, 0.12198921, -0.16449445, 0.08773425, 0.30054978) * go_0(0.0, 0.0);\n    result += mat4(-0.10413989, -0.10316161, 0.04342709, -0.021252686, 0.120892406, 0.37798002, -0.35963747, 0.021069285, 0.37587845, -0.08159587, 0.011139747, 0.2501104, -0.094568014, 0.037900843, -0.025109999, -0.030106556) * go_0(0.0, 1.0);\n    result += mat4(0.09680291, -0.040868275, 0.051731605, 0.089064725, -0.56098557, -0.38148618, -0.017037416, 0.08508287, -0.019247344, 0.019857002, -0.03512887, 0.031057188, -0.09648583, -0.04474188, 0.028748507, -0.11880965) * go_0(1.0, -1.0);\n    result += mat4(-0.010236943, 0.04257042, -0.08202597, -0.004203426, -0.26801527, -0.11716526, -0.017402772, -0.05819106, -0.13394608, 0.0234606, -0.15404865, -0.06801164, -0.0047627664, -0.1975249, 0.09420144, 0.23249897) * go_0(1.0, 0.0);\n    result += mat4(0.107361935, 0.07373787, 0.06242962, 0.05236332, -0.028867323, 0.025924044, -0.042526353, -0.0015729597, -0.1323144, -0.4040712, 0.023919407, -0.09535502, 0.049100045, 0.081110805, 0.08946112, 0.058505684) * go_0(1.0, 1.0);\n    result += mat4(0.13236825, -0.04468476, -0.04426802, 0.031087106, -0.09093992, -0.07470971, -0.01591504, 0.05924266, -0.21910913, 0.065537, -0.18358919, -0.02533145, -0.1512009, -0.04953928, 0.015540006, -0.0043442883) * go_1(-1.0, -1.0);\n    result += mat4(-0.14016777, -0.1086958, 0.16316028, 0.050777458, 0.23148167, 0.04944809, -0.10599886, -0.10447021, -0.40729257, -0.10926556, 0.069055155, 0.110635415, 0.108922414, -0.1716362, 0.10743909, -0.102534756) * go_1(-1.0, 0.0);\n    result += mat4(0.017795928, -0.066930935, 0.09396082, 0.092585504, 0.14223933, 0.059458215, 0.072033696, -0.04507726, -0.19956456, 0.1251282, -0.31733638, -0.10465904, 0.08546377, 0.048638333, 0.031372465, -0.08720661) * go_1(-1.0, 1.0);\n    result += mat4(0.108719654, -0.092161916, -0.014724377, 0.20068261, -0.24350016, 0.2113636, -0.07483714, -0.45665312, -0.25134233, 0.2753893, -0.11324696, -0.04472, 0.1576102, -0.045395147, 0.06013951, -0.12507361) * go_1(0.0, -1.0);\n    result += mat4(0.546225, -0.281897, 0.19477816, -0.116612464, -0.3145171, -0.41660902, 0.333625, 0.35902345, 0.48333502, 0.4662005, 0.10222491, -0.15314859, -0.3036888, 0.22849742, 0.20740797, 0.41399437) * go_1(0.0, 0.0);\n    result += mat4(0.007284074, 0.0393942, -0.31192186, -0.15687793, -0.289214, -0.015956698, -0.24718472, -0.1637855, -0.00765037, 0.26677555, 0.20215511, 0.37790874, -0.22096673, 0.25287116, -0.2446764, -0.13610223) * go_1(0.0, 1.0);\n    result += mat4(-0.16734968, 0.16721225, -0.053508647, -0.041097626, 0.062356673, 0.07812319, -0.263546, -0.39739034, 0.003389846, 0.12676363, -0.13175991, -0.19019242, -0.011847587, -0.007580052, -0.023946386, 0.046034034) * go_1(1.0, -1.0);\n    result += mat4(-0.17047611, 0.13298693, -0.07506747, -0.045542978, 0.33571973, 0.20192616, 0.30674616, 0.25668672, -0.24134545, 0.031693842, -0.009647641, 0.040534843, 0.03159419, -0.1100516, 0.11371316, 0.06098735) * go_1(1.0, 0.0);\n    result += mat4(-0.05518961, 0.19402988, -0.09646874, -0.059196774, -0.0073436056, -0.1381309, 0.06868669, 0.061328378, -0.1480867, -0.15774113, -0.022572191, 0.122521356, -0.04067007, -0.10145177, 0.13006335, -0.099452734) * go_1(1.0, 1.0);\n    result += mat4(0.06962972, 0.07768411, 0.021085173, 0.108355984, -0.03132525, 0.10220273, -0.11626593, -0.14104277, 0.018778645, -0.024237925, 0.048783034, 0.09074447, 0.4120426, -0.01948466, 0.073218934, 0.055681944) * go_2(-1.0, -1.0);\n    result += mat4(-0.22553118, -0.12923603, -0.22068842, -0.35037905, 0.005709937, -0.09528472, 0.08718399, 0.13200706, 0.17220478, 0.096844435, -0.30439013, -0.14122063, 0.15733318, -0.1014675, 0.33836862, 0.042193163) * go_2(-1.0, 0.0);\n    result += mat4(0.15826897, -0.034870047, 0.09295099, -0.17674965, -0.042326324, 0.06680338, -0.074267656, -0.0631393, -0.11267909, -0.19795708, 0.22005288, 0.35703793, 0.033995766, -0.12663686, -0.02449896, -0.123250045) * go_2(-1.0, 1.0);\n    result += mat4(0.021434195, 0.058398597, 0.04828315, -0.0016824572, -0.04291545, -0.0744907, -0.07698706, -0.15937585, -0.18852457, -0.17966963, 0.023800725, 0.025979731, -0.51412296, -0.018316887, -0.23076254, -0.12298674) * go_2(0.0, -1.0);\n    result += mat4(0.16054317, -0.0002730893, -0.54173076, -0.62443435, 0.04300197, -0.08529622, 0.15392275, 0.15742144, 0.025834514, -0.2800517, -0.17600477, 0.0020806703, -0.3010582, 0.45233512, 0.25595665, 0.103661336) * go_2(0.0, 0.0);\n    result += mat4(-0.024034392, -0.43800178, 0.28606912, -0.20908915, 0.078471914, -0.030501373, -0.059055753, 0.050494444, 0.063274644, -0.025071034, 0.17561312, -0.100698635, -0.25631955, 0.039981876, -0.18506624, 0.08366402) * go_2(0.0, 1.0);\n    result += mat4(-0.1413656, 0.03589635, -0.020917566, 0.017598262, 0.020156413, -0.018854238, 0.027228508, -0.03806087, -0.021715842, 0.071974196, -0.040065665, 0.08459291, -0.23530225, 0.16599682, -0.2772327, 0.10041177) * go_2(1.0, -1.0);\n    result += mat4(-0.055056706, 0.1286236, -0.11890451, -0.1790546, 0.16517544, -0.040448934, 0.12548013, 0.017075695, 0.07185459, -0.13236302, 0.19354409, 0.12767012, 0.31120765, 0.16378082, -0.036915366, -0.19724306) * go_2(1.0, 0.0);\n    result += mat4(-0.02225051, 0.033263147, 0.003279449, 0.08826271, -0.047833472, 6.574577e-05, 0.13721916, 0.04801998, -0.014958419, 0.08791209, -0.08076282, 0.024002168, -0.18028922, 0.23835851, -0.23309888, -0.119310364) * go_2(1.0, 1.0);\n    result += mat4(0.044960875, 0.18821983, 0.027640678, 0.013462449, 0.19011214, 0.21559924, -0.03329638, 0.07234414, 0.030880248, -0.11273214, 0.102028474, 0.12203351, 0.035855662, 0.008828778, 0.007218363, -0.012421797) * go_3(-1.0, -1.0);\n    result += mat4(-0.09450626, 0.025191775, -0.10738468, 0.16237053, 0.073676676, 0.12488881, -0.048748355, 0.007877263, 0.3572506, -0.07911043, 0.14684045, 0.0015310893, -0.33411503, -0.1151223, 0.004201752, 0.017775744) * go_3(-1.0, 0.0);\n    result += mat4(-0.10607509, -0.008143826, -0.08448629, -0.27557802, 0.0046665915, 0.008158659, 0.030826218, 0.020516023, 0.2333065, -0.017463414, -0.041772116, -0.03027809, -0.028166672, -0.080471426, 0.048199337, 0.08341059) * go_3(-1.0, 1.0);\n    result += mat4(-0.14640257, -0.18334304, -0.061674733, 0.0008892598, -0.2374775, -0.2721524, -0.040371176, 0.26362613, 0.19872928, -0.11246391, 0.0842288, 0.11188515, 0.0045209546, -0.04250933, -0.0738212, -0.069005966) * go_3(0.0, -1.0);\n    result += mat4(-0.08760266, 0.4816288, -0.21241407, 0.22734411, -0.1783721, -0.26842996, 0.099888, -0.2867675, 0.085521065, -0.3780281, -0.018543908, -0.039699722, 0.75688565, -0.5333645, 0.47567275, 0.09518891) * go_3(0.0, 0.0);\n    result += mat4(-0.04072665, 0.05998423, -0.48314768, -0.29495844, 0.10358383, -0.09816629, 0.028586809, -0.047708735, 0.008320228, 0.04089551, -0.18359782, -0.27615002, 0.12414414, -0.072417594, 0.25932562, 0.30268723) * go_3(0.0, 1.0);\n    result += mat4(0.14481631, 0.06484443, -0.09898657, -0.06553556, 0.25750044, -0.07265585, 0.12903488, -0.022347894, -0.04693863, -0.000107379274, 0.030295763, -0.0325354, 0.086214684, -0.021326948, 0.039682828, -0.034843277) * go_3(1.0, -1.0);\n    result += mat4(-0.031971477, -0.25145087, 0.03931631, 0.14262606, -0.06044626, 0.22820354, -0.10506207, 0.18064679, 0.0069641788, 0.01477993, -0.003626875, 0.118767865, 0.109416224, -0.002998205, 0.035680585, 0.07843882) * go_3(1.0, 0.0);\n    result += mat4(0.03375426, -0.059815384, 0.11632834, -0.12411481, 0.022583738, 0.02544465, -0.054889992, -0.07031964, -0.10140042, 0.16750422, -0.1448294, -0.09316004, 0.035582513, -0.026138382, -0.031955894, 0.040148776) * go_3(1.0, 1.0);\n    result += vec4(-0.03573331, 0.032919675, 0.011109369, 0.008329268);\n    return result;\n}\n//!DESC Anime4K-v3.2-Upscale-CNN-x2-(VL)-Conv-4x1x1x112\n//!HOOK MAIN\n//!BIND conv2d_tf\n//!BIND conv2d_tf1\n//!BIND conv2d_1_tf\n//!BIND conv2d_1_tf1\n//!BIND conv2d_2_tf\n//!BIND conv2d_2_tf1\n//!BIND conv2d_3_tf\n//!BIND conv2d_3_tf1\n//!BIND conv2d_4_tf\n//!BIND conv2d_4_tf1\n//!BIND conv2d_5_tf\n//!BIND conv2d_5_tf1\n//!BIND conv2d_6_tf\n//!BIND conv2d_6_tf1\n//!SAVE conv2d_last_tf\n//!WIDTH conv2d_tf.w\n//!HEIGHT conv2d_tf.h\n//!COMPONENTS 4\n//!WHEN OUTPUT.w MAIN.w / 1.200 > OUTPUT.h MAIN.h / 1.200 > *\n#define g_0 (max((conv2d_tf_tex(conv2d_tf_pos)), 0.0))\n#define g_1 (max((conv2d_tf1_tex(conv2d_tf1_pos)), 0.0))\n#define g_2 (max(-(conv2d_tf_tex(conv2d_tf_pos)), 0.0))\n#define g_3 (max(-(conv2d_tf1_tex(conv2d_tf1_pos)), 0.0))\n#define g_4 (max((conv2d_1_tf_tex(conv2d_1_tf_pos)), 0.0))\n#define g_5 (max((conv2d_1_tf1_tex(conv2d_1_tf1_pos)), 0.0))\n#define g_6 (max(-(conv2d_1_tf_tex(conv2d_1_tf_pos)), 0.0))\n#define g_7 (max(-(conv2d_1_tf1_tex(conv2d_1_tf1_pos)), 0.0))\n#define g_8 (max((conv2d_2_tf_tex(conv2d_2_tf_pos)), 0.0))\n#define g_9 (max((conv2d_2_tf1_tex(conv2d_2_tf1_pos)), 0.0))\n#define g_10 (max(-(conv2d_2_tf_tex(conv2d_2_tf_pos)), 0.0))\n#define g_11 (max(-(conv2d_2_tf1_tex(conv2d_2_tf1_pos)), 0.0))\n#define g_12 (max((conv2d_3_tf_tex(conv2d_3_tf_pos)), 0.0))\n#define g_13 (max((conv2d_3_tf1_tex(conv2d_3_tf1_pos)), 0.0))\n#define g_14 (max(-(conv2d_3_tf_tex(conv2d_3_tf_pos)), 0.0))\n#define g_15 (max(-(conv2d_3_tf1_tex(conv2d_3_tf1_pos)), 0.0))\n#define g_16 (max((conv2d_4_tf_tex(conv2d_4_tf_pos)), 0.0))\n#define g_17 (max((conv2d_4_tf1_tex(conv2d_4_tf1_pos)), 0.0))\n#define g_18 (max(-(conv2d_4_tf_tex(conv2d_4_tf_pos)), 0.0))\n#define g_19 (max(-(conv2d_4_tf1_tex(conv2d_4_tf1_pos)), 0.0))\n#define g_20 (max((conv2d_5_tf_tex(conv2d_5_tf_pos)), 0.0))\n#define g_21 (max((conv2d_5_tf1_tex(conv2d_5_tf1_pos)), 0.0))\n#define g_22 (max(-(conv2d_5_tf_tex(conv2d_5_tf_pos)), 0.0))\n#define g_23 (max(-(conv2d_5_tf1_tex(conv2d_5_tf1_pos)), 0.0))\n#define g_24 (max((conv2d_6_tf_tex(conv2d_6_tf_pos)), 0.0))\n#define g_25 (max((conv2d_6_tf1_tex(conv2d_6_tf1_pos)), 0.0))\n#define g_26 (max(-(conv2d_6_tf_tex(conv2d_6_tf_pos)), 0.0))\n#define g_27 (max(-(conv2d_6_tf1_tex(conv2d_6_tf1_pos)), 0.0))\nvec4 hook() {\n    vec4 result = mat4(-0.11498094, -0.053904895, -0.11520678, -0.05479549, 0.028396055, 0.032767884, 0.052479446, 0.05257866, -0.25706592, -0.3454966, -0.24713765, -0.2854201, -0.10287636, 0.0023146886, -0.09190338, -0.011193905) * g_0;\n    result += mat4(-0.05461422, 0.008780496, -0.07738697, -0.032230727, -0.047554165, -0.025061952, -0.051897213, -0.009545297, -0.14548294, -0.15184018, -0.01313442, -0.015299784, -0.0007883845, -0.12866738, -0.15260352, -0.27081275) * g_1;\n    result += mat4(0.11007706, 0.035344437, 0.11020841, 0.0425353, 0.1613199, 0.18417408, 0.09274313, 0.11943135, 0.106862, 0.079875536, 0.0937752, 0.068030775, 0.029093558, -0.06441164, 0.06467169, -0.021989612) * g_2;\n    result += mat4(0.049548414, -0.012455486, 0.07185561, 0.021865537, 0.020969186, -0.03374196, -0.024260623, -0.07739141, 0.07164591, 0.12741035, 0.0379913, 0.076403245, 0.07049977, 0.0744538, 0.0062989634, 0.01818882) * g_3;\n    result += mat4(-0.12511204, -0.010836819, 0.13709816, 0.22472954, 0.21280868, -0.006484726, 0.17554289, -0.009977173, 0.078398876, 0.20698707, 0.13432744, 0.29740283, -0.24750128, -0.32757792, -0.19807857, -0.2537023) * g_4;\n    result += mat4(-0.27207088, -0.1385644, -0.2166476, -0.07687419, -0.20300622, -0.29678395, -0.13135734, -0.20851587, 0.0361364, 0.011243289, -0.06845459, -0.11796941, 0.11575868, 0.070215136, -0.10295678, -0.12281369) * g_5;\n    result += mat4(0.13619795, -0.0019436983, -0.12701888, -0.25933513, -0.20134166, 0.00062823144, -0.076756015, 0.11002947, 0.0059049693, -0.18756741, -0.0718802, -0.2589954, 0.23413423, 0.30107784, 0.14445266, 0.18920745) * g_6;\n    result += mat4(0.1494216, 0.0587532, 0.05478662, -0.039123338, 0.23322394, 0.29950607, 0.24384268, 0.27843767, -0.16094431, -0.04705998, -0.016345032, 0.028868208, -0.102872886, -0.04659664, 0.104105346, 0.14305067) * g_7;\n    result += mat4(-0.001037014, 0.010001526, -0.0052278573, 0.024779709, 0.06857274, 0.067640975, 0.085439384, 0.09242789, -0.066597246, -0.055928994, 0.0015658981, 0.016131008, -0.03524695, -0.018364554, -0.047754433, -0.014295886) * g_8;\n    result += mat4(-0.042207, 0.02835915, -0.1404656, -0.08563323, -0.030979915, -0.0673764, 0.10733943, 0.057902794, 0.00022424995, -0.0023634837, -0.10778953, -0.10202357, -0.020368295, -0.019088887, -0.06875738, -0.08504131) * g_9;\n    result += mat4(-0.00043458896, 0.00045652856, -0.02016843, -0.020062413, -0.08740103, -0.042085808, -0.10644177, -0.09226477, 0.11212161, -0.00048174805, 0.021872435, -0.05868698, 0.0333954, 0.058184672, 0.05532576, 0.07621587) * g_10;\n    result += mat4(0.054245148, 0.001020329, 0.09106849, 0.05303779, 0.009889632, 0.01309413, -0.09187347, -0.08618193, -0.011621187, 0.016222361, 0.061095525, 0.060885344, 0.078050986, 0.0111776795, 0.08829944, 0.032022282) * g_11;\n    result += mat4(0.01643529, 0.02285545, -0.03498564, 0.00769657, -0.0042474116, 0.015836312, -0.025771018, -0.0016368, -0.008897948, -0.012588166, -0.01416411, -0.003578984, 0.025991246, 0.021237152, 0.017450012, 0.025172485) * g_12;\n    result += mat4(0.014568868, 0.017796224, -0.036679734, -0.03138748, 0.019457601, -0.027607411, -0.004529679, -0.038048342, -0.054055385, -0.03876025, 0.041948095, 0.005869784, 0.02439633, 0.05177997, 0.016000897, 0.0057169925) * g_13;\n    result += mat4(-0.03021866, 0.017678728, -0.01371109, 0.013548159, -0.0038099394, -0.014066414, 0.028093752, 0.0027308422, -0.010615999, 0.012673458, -0.03028171, -0.016818244, -0.06530097, -0.018845048, -0.0072947564, -0.0038243714) * g_14;\n    result += mat4(-0.019006258, -0.007847591, 0.03690709, 0.06714211, 0.0073993434, -0.009766907, -0.0021441753, -0.01308625, 0.06658726, 0.06701995, -0.027305668, -0.016032105, -0.028976806, -0.0036668575, -0.0027825525, 0.0105632655) * g_15;\n    result += mat4(0.028945107, -0.0014701135, 0.048950657, -0.01923516, -0.0014054152, 0.002650635, -0.005300331, 0.004860559, 0.011158468, 0.005940625, -0.012095051, 0.0041518128, -0.020433836, -0.025870577, -0.0007547932, -0.026509356) * g_16;\n    result += mat4(-0.004545374, 0.04264545, 0.021741537, 0.029115127, 0.04225599, -0.0055392785, 0.026570829, -0.031795148, -0.008307126, 0.020176455, 0.010904648, 0.017765503, -0.10806103, -0.01776947, 0.00070428237, -0.06356262) * g_17;\n    result += mat4(-0.05663172, 0.05908046, -0.03837452, 0.06636983, -0.007960516, -0.06384041, 0.023125881, -0.030108837, 0.0038054318, -0.023263922, 0.020264054, -0.0062937695, 0.031630237, 0.020909082, 0.03594235, 0.035879835) * g_18;\n    result += mat4(-0.0050448794, 0.033650696, -0.002830413, 0.035174295, -0.024521282, 0.013054315, -0.020833842, 0.037953895, 0.08249671, 0.024239466, -0.012758333, -0.027316988, 0.051040914, 0.0005025873, 0.039778862, 0.0024668393) * g_19;\n    result += mat4(0.017232442, 0.022482058, 0.020233413, 0.024337437, 0.07986929, 0.06234036, 0.12662584, -0.05271183, -0.009718745, -0.0046989853, -0.0030333172, -0.04034237, -0.0113442, 0.022746231, -0.035293855, -0.009433693) * g_20;\n    result += mat4(0.015766997, 0.013647276, -0.029327558, 0.039106004, -0.010398323, -0.032851525, 0.02908329, -0.003789618, 0.12963496, 0.010851003, 0.1126276, -0.049255487, 0.06867432, 0.07970792, 0.017840397, -0.026481882) * g_21;\n    result += mat4(-0.058729574, -0.07886952, 0.033267397, 0.02755372, -0.0172006, 0.012404398, -0.0230168, -0.015059758, -0.09239916, -0.029533267, -0.043251917, 0.0035152994, 0.022931995, 0.101714484, -0.044946067, 0.094993) * g_22;\n    result += mat4(-0.04708704, -0.032475296, -0.03228093, -0.08810475, 0.013745045, 0.027828002, -0.031922746, 0.022986397, -0.061620213, -0.03694645, -0.055026993, 0.0031291894, -0.028799903, -0.0025357977, -0.03441407, 0.0028600092) * g_23;\n    result += mat4(0.058981724, -0.10447273, -0.088705614, 0.16546178, -0.023549391, -0.008831522, -0.018411588, 0.029640056, -0.068086684, -0.05414636, -0.029401174, 0.036180343, -0.031988926, -0.047249753, 0.008162177, 0.00548062) * g_24;\n    result += mat4(0.05287462, -0.030657746, 0.02821435, 0.037005343, 0.03534311, -0.15614955, 0.07085459, -0.11997641, -0.009156166, -0.021968868, -0.054147746, -0.07307657, -0.006428544, -0.017528288, 0.012614676, 0.037840024) * g_25;\n    result += mat4(-0.021977803, 0.047799855, 0.02660416, -0.07292106, 0.045195807, -0.0056674764, 0.10824326, -0.112114795, 0.1447127, -0.0119616175, 0.0011661504, -0.04553905, 0.13048342, 0.14574122, -0.105522245, -0.102792375) * g_26;\n    result += mat4(-0.16397473, 0.15785863, -0.06666504, -0.01682913, 0.06070918, 0.070222184, 0.037701584, 0.026657054, -0.0835267, -0.009457008, 0.13232987, 0.13508691, -0.056414206, -0.06818828, 0.079076104, 0.032249212) * g_27;\n    result += vec4(-0.10795144, -0.09953324, -0.055413827, -0.03875493);\n    return result;\n}\n//!DESC Anime4K-v3.2-Upscale-CNN-x2-(VL)-Conv-4x1x1x112\n//!HOOK MAIN\n//!BIND conv2d_tf\n//!BIND conv2d_tf1\n//!BIND conv2d_1_tf\n//!BIND conv2d_1_tf1\n//!BIND conv2d_2_tf\n//!BIND conv2d_2_tf1\n//!BIND conv2d_3_tf\n//!BIND conv2d_3_tf1\n//!BIND conv2d_4_tf\n//!BIND conv2d_4_tf1\n//!BIND conv2d_5_tf\n//!BIND conv2d_5_tf1\n//!BIND conv2d_6_tf\n//!BIND conv2d_6_tf1\n//!SAVE conv2d_last_tf1\n//!WIDTH conv2d_tf.w\n//!HEIGHT conv2d_tf.h\n//!COMPONENTS 4\n//!WHEN OUTPUT.w MAIN.w / 1.200 > OUTPUT.h MAIN.h / 1.200 > *\n#define g_0 (max((conv2d_tf_tex(conv2d_tf_pos)), 0.0))\n#define g_1 (max((conv2d_tf1_tex(conv2d_tf1_pos)), 0.0))\n#define g_2 (max(-(conv2d_tf_tex(conv2d_tf_pos)), 0.0))\n#define g_3 (max(-(conv2d_tf1_tex(conv2d_tf1_pos)), 0.0))\n#define g_4 (max((conv2d_1_tf_tex(conv2d_1_tf_pos)), 0.0))\n#define g_5 (max((conv2d_1_tf1_tex(conv2d_1_tf1_pos)), 0.0))\n#define g_6 (max(-(conv2d_1_tf_tex(conv2d_1_tf_pos)), 0.0))\n#define g_7 (max(-(conv2d_1_tf1_tex(conv2d_1_tf1_pos)), 0.0))\n#define g_8 (max((conv2d_2_tf_tex(conv2d_2_tf_pos)), 0.0))\n#define g_9 (max((conv2d_2_tf1_tex(conv2d_2_tf1_pos)), 0.0))\n#define g_10 (max(-(conv2d_2_tf_tex(conv2d_2_tf_pos)), 0.0))\n#define g_11 (max(-(conv2d_2_tf1_tex(conv2d_2_tf1_pos)), 0.0))\n#define g_12 (max((conv2d_3_tf_tex(conv2d_3_tf_pos)), 0.0))\n#define g_13 (max((conv2d_3_tf1_tex(conv2d_3_tf1_pos)), 0.0))\n#define g_14 (max(-(conv2d_3_tf_tex(conv2d_3_tf_pos)), 0.0))\n#define g_15 (max(-(conv2d_3_tf1_tex(conv2d_3_tf1_pos)), 0.0))\n#define g_16 (max((conv2d_4_tf_tex(conv2d_4_tf_pos)), 0.0))\n#define g_17 (max((conv2d_4_tf1_tex(conv2d_4_tf1_pos)), 0.0))\n#define g_18 (max(-(conv2d_4_tf_tex(conv2d_4_tf_pos)), 0.0))\n#define g_19 (max(-(conv2d_4_tf1_tex(conv2d_4_tf1_pos)), 0.0))\n#define g_20 (max((conv2d_5_tf_tex(conv2d_5_tf_pos)), 0.0))\n#define g_21 (max((conv2d_5_tf1_tex(conv2d_5_tf1_pos)), 0.0))\n#define g_22 (max(-(conv2d_5_tf_tex(conv2d_5_tf_pos)), 0.0))\n#define g_23 (max(-(conv2d_5_tf1_tex(conv2d_5_tf1_pos)), 0.0))\n#define g_24 (max((conv2d_6_tf_tex(conv2d_6_tf_pos)), 0.0))\n#define g_25 (max((conv2d_6_tf1_tex(conv2d_6_tf1_pos)), 0.0))\n#define g_26 (max(-(conv2d_6_tf_tex(conv2d_6_tf_pos)), 0.0))\n#define g_27 (max(-(conv2d_6_tf1_tex(conv2d_6_tf1_pos)), 0.0))\nvec4 hook() {\n    vec4 result = mat4(0.024905335, -0.0020974763, 0.02695263, 0.00016802056, -0.024053082, -0.02133723, -0.031614035, -0.031826317, 0.120421864, 0.10555479, 0.08609448, 0.116875134, 0.046175968, 0.04224941, 0.059216674, 0.035143953) * g_0;\n    result += mat4(0.059397914, 0.016519934, 0.07189327, 0.047407165, 0.04808963, 0.02792908, 0.057017103, 0.034324065, 0.14228246, 0.11275426, 0.088058695, 0.059600517, 0.02063494, 0.052596953, 0.047207687, 0.08789091) * g_1;\n    result += mat4(-0.013453174, 0.008474715, -0.017593835, 0.009218917, 0.070580654, 0.040542338, 0.08812338, 0.074653216, -0.016356857, 0.015809007, -0.008739107, 0.0097674895, -0.018381525, -0.007775341, -0.040571664, -0.011188163) * g_2;\n    result += mat4(-0.026196122, -0.034825727, -0.042998232, -0.033436514, -0.01678153, -0.004592797, -0.010311677, 0.0008815291, -0.08899181, -0.10274026, -0.066960976, -0.082430154, -0.057137426, -0.07554528, -0.030993424, -0.050372377) * g_3;\n    result += mat4(0.022921838, -0.010479244, -0.050794605, -0.073633075, -0.053708922, 0.009594084, -0.071259, -0.01054356, 0.005165821, -0.08024963, -0.049251772, -0.09581235, 0.17995799, 0.09743011, 0.13533138, 0.11643848) * g_4;\n    result += mat4(0.09727046, 0.07292666, 0.06820908, 0.041535784, -0.0049705, 0.0048759184, -0.035702795, -0.015944308, -0.010730028, 0.018847652, 0.06466244, 0.086318985, -0.05661574, -0.040698618, 0.010839972, 0.0027009705) * g_5;\n    result += mat4(-0.04628466, 0.010060396, 0.02609333, 0.08664702, 0.057045907, 0.033591177, 0.02186063, -0.024303377, 0.006569828, 0.08025825, 0.016128821, 0.10180713, -0.12228169, -0.112990454, -0.078443415, -0.09126021) * g_6;\n    result += mat4(-0.12733299, -0.087755, -0.07374111, -0.044979006, -0.025347412, -0.004083168, 0.023782173, 0.02900392, -0.017815407, -0.041119996, -0.057978686, -0.13521095, 0.08364004, 0.06950181, 0.023554614, 0.008043734) * g_7;\n    result += mat4(0.009062775, -0.003570175, -0.007378757, -0.0018487388, 0.01145638, 0.05217187, -0.008250244, 0.008433307, -0.056756936, -0.044681005, -0.08096105, -0.08033185, -0.023784965, -0.01859799, 0.013042476, 0.021188647) * g_8;\n    result += mat4(-0.0071619656, -0.012498299, -0.05144986, -0.078112476, -0.034992415, -0.017038302, -0.04464615, -0.044504963, 0.024249, -0.004297534, 0.03674578, 0.03090718, 0.04698553, 0.008344952, 0.057619847, -0.0338724) * g_9;\n    result += mat4(-0.011845145, -0.0045043705, -1.6646482e-06, -0.0038495932, -0.01992515, 0.004827126, 0.019493148, 0.00862289, 0.10151322, 0.0021909082, 0.09940764, 0.03728846, 0.027824005, 0.04358071, 0.014909185, 0.036326095) * g_10;\n    result += mat4(0.022513246, 0.028257169, 0.0102195935, 0.03301329, 0.052253865, -0.0021944977, 0.08247392, 0.03256867, -0.040685873, -0.0052207555, -0.0451257, -0.054165114, 0.01647699, 0.0028809097, -0.015233776, -0.0008741886) * g_11;\n    result += mat4(0.017371105, 0.01597189, -0.052552313, -0.008554715, -0.0023150423, 0.006076517, -0.012868931, 0.0039361073, -0.007524978, -0.004284313, -0.021520883, -0.010327569, 0.02543678, 0.008725823, -0.0073885336, 0.005528395) * g_12;\n    result += mat4(0.019192757, 0.016561812, 0.0027538154, 0.0013078215, 0.007916496, -0.042525183, -0.013173432, -0.05265476, -0.062195376, -0.011255499, 0.020898128, 0.021532273, -0.001524097, 0.034835674, -0.004051403, -0.0292426) * g_13;\n    result += mat4(-0.049191684, -9.43322e-06, -0.009106849, 0.012845289, -0.019482708, -0.011163468, 0.0034011535, -0.007062845, -0.006469714, 0.03177786, -0.033006195, -0.0006813464, -0.053963087, 0.00085209147, 0.02734121, 0.034086403) * g_14;\n    result += mat4(-0.03232248, -0.004037002, -0.010319106, 0.030889064, 0.019604538, 0.0020888883, 0.010277864, 0.000661223, 0.057915937, 0.030683514, 0.00042533095, -0.013019287, -0.015896408, 0.0038484468, -0.0042103594, 0.02174542) * g_15;\n    result += mat4(0.032975145, 0.0011456647, 0.04913679, -0.017063798, 0.0117176045, 0.007440557, 0.0020480808, 0.009415731, 0.027573857, 0.015140836, -0.01679426, -0.006124731, -0.03206279, -0.029842237, -0.010428016, -0.028513178) * g_16;\n    result += mat4(-0.00506859, 0.055869613, 0.010164368, 0.027031485, 0.042289548, -0.0054258504, 0.032214936, -0.029970925, -0.0058315448, 0.022889478, 0.01681123, 0.02985076, -0.111186065, -0.02202099, 0.0030994313, -0.062343158) * g_17;\n    result += mat4(-0.060951103, 0.06079555, -0.0396464, 0.070911355, -0.011480358, -0.06803282, 0.01637355, -0.043100975, -0.00423709, -0.028337711, 0.021635853, 0.0014857082, 0.030084312, 0.018155476, 0.043694943, 0.038795974) * g_18;\n    result += mat4(-0.0060662925, 0.029721662, -0.008117774, 0.034551267, -0.024477571, 0.018841071, -0.027095588, 0.034495078, 0.082398005, 0.008998768, -0.016399248, -0.043801688, 0.05936684, 0.006066549, 0.045399766, 3.5319943e-05) * g_19;\n    result += mat4(0.019259382, 0.02494012, 0.029301709, 0.028329274, 0.09122267, 0.06900443, 0.1412115, -0.043169618, -0.01627418, -0.004989528, -0.0042651827, -0.04556752, -0.023623291, 0.013007996, -0.04483056, -0.015727345) * g_20;\n    result += mat4(0.016332543, 0.016384754, -0.030676385, 0.045312885, -0.0100853555, -0.032632045, 0.031514473, -0.0070776115, 0.13642761, 0.0023589598, 0.12214136, -0.062155515, 0.08240989, 0.08894205, 0.03325406, -0.016589595) * g_21;\n    result += mat4(-0.06494277, -0.08158925, 0.030425413, 0.019835634, -0.012624623, 0.013942616, -0.030527417, -0.021668324, -0.09444672, -0.033064254, -0.044167448, 0.0011024752, 0.03210801, 0.12662941, -0.03912534, 0.1112649) * g_22;\n    result += mat4(-0.04716062, -0.03751481, -0.031030515, -0.09067383, 0.0077815712, 0.02169541, -0.035285182, 0.02290573, -0.0704085, -0.03916127, -0.058103334, 0.004915147, -0.0333844, -0.011548617, -0.031151932, -0.00043817286) * g_23;\n    result += mat4(0.05976319, -0.107285, -0.097245865, 0.17706421, -0.021453341, -0.0047738464, -0.017621001, 0.033400454, -0.07225561, -0.05599672, -0.027600193, 0.038664024, -0.03762786, -0.052429967, 0.0104017975, 0.007116869) * g_24;\n    result += mat4(0.06014114, -0.029824806, 0.03209269, 0.04392036, 0.031300627, -0.16249833, 0.06878509, -0.12658615, -0.012383169, -0.025043553, -0.06527381, -0.08149099, -0.014006842, -0.018669648, 0.014510818, 0.042045828) * g_25;\n    result += mat4(-0.023342922, 0.047104675, 0.029629575, -0.082307704, 0.04035797, -0.0013049254, 0.11085582, -0.11031226, 0.14778149, -0.016699014, -0.00634342, -0.055320874, 0.14306462, 0.15896587, -0.110229075, -0.1069649) * g_26;\n    result += mat4(-0.17449625, 0.15787153, -0.06711028, -0.023110518, 0.06862914, 0.074063435, 0.042682912, 0.029800726, -0.08768606, -0.009814701, 0.14180017, 0.14780663, -0.05672417, -0.074305914, 0.07873489, 0.028458012) * g_27;\n    result += vec4(0.06026231, 0.040204916, 0.037672628, 0.023496555);\n    return result;\n}\n//!DESC Anime4K-v3.2-Upscale-CNN-x2-(VL)-Conv-4x1x1x112\n//!HOOK MAIN\n//!BIND conv2d_tf\n//!BIND conv2d_tf1\n//!BIND conv2d_1_tf\n//!BIND conv2d_1_tf1\n//!BIND conv2d_2_tf\n//!BIND conv2d_2_tf1\n//!BIND conv2d_3_tf\n//!BIND conv2d_3_tf1\n//!BIND conv2d_4_tf\n//!BIND conv2d_4_tf1\n//!BIND conv2d_5_tf\n//!BIND conv2d_5_tf1\n//!BIND conv2d_6_tf\n//!BIND conv2d_6_tf1\n//!SAVE conv2d_last_tf2\n//!WIDTH conv2d_tf.w\n//!HEIGHT conv2d_tf.h\n//!COMPONENTS 4\n//!WHEN OUTPUT.w MAIN.w / 1.200 > OUTPUT.h MAIN.h / 1.200 > *\n#define g_0 (max((conv2d_tf_tex(conv2d_tf_pos)), 0.0))\n#define g_1 (max((conv2d_tf1_tex(conv2d_tf1_pos)), 0.0))\n#define g_2 (max(-(conv2d_tf_tex(conv2d_tf_pos)), 0.0))\n#define g_3 (max(-(conv2d_tf1_tex(conv2d_tf1_pos)), 0.0))\n#define g_4 (max((conv2d_1_tf_tex(conv2d_1_tf_pos)), 0.0))\n#define g_5 (max((conv2d_1_tf1_tex(conv2d_1_tf1_pos)), 0.0))\n#define g_6 (max(-(conv2d_1_tf_tex(conv2d_1_tf_pos)), 0.0))\n#define g_7 (max(-(conv2d_1_tf1_tex(conv2d_1_tf1_pos)), 0.0))\n#define g_8 (max((conv2d_2_tf_tex(conv2d_2_tf_pos)), 0.0))\n#define g_9 (max((conv2d_2_tf1_tex(conv2d_2_tf1_pos)), 0.0))\n#define g_10 (max(-(conv2d_2_tf_tex(conv2d_2_tf_pos)), 0.0))\n#define g_11 (max(-(conv2d_2_tf1_tex(conv2d_2_tf1_pos)), 0.0))\n#define g_12 (max((conv2d_3_tf_tex(conv2d_3_tf_pos)), 0.0))\n#define g_13 (max((conv2d_3_tf1_tex(conv2d_3_tf1_pos)), 0.0))\n#define g_14 (max(-(conv2d_3_tf_tex(conv2d_3_tf_pos)), 0.0))\n#define g_15 (max(-(conv2d_3_tf1_tex(conv2d_3_tf1_pos)), 0.0))\n#define g_16 (max((conv2d_4_tf_tex(conv2d_4_tf_pos)), 0.0))\n#define g_17 (max((conv2d_4_tf1_tex(conv2d_4_tf1_pos)), 0.0))\n#define g_18 (max(-(conv2d_4_tf_tex(conv2d_4_tf_pos)), 0.0))\n#define g_19 (max(-(conv2d_4_tf1_tex(conv2d_4_tf1_pos)), 0.0))\n#define g_20 (max((conv2d_5_tf_tex(conv2d_5_tf_pos)), 0.0))\n#define g_21 (max((conv2d_5_tf1_tex(conv2d_5_tf1_pos)), 0.0))\n#define g_22 (max(-(conv2d_5_tf_tex(conv2d_5_tf_pos)), 0.0))\n#define g_23 (max(-(conv2d_5_tf1_tex(conv2d_5_tf1_pos)), 0.0))\n#define g_24 (max((conv2d_6_tf_tex(conv2d_6_tf_pos)), 0.0))\n#define g_25 (max((conv2d_6_tf1_tex(conv2d_6_tf1_pos)), 0.0))\n#define g_26 (max(-(conv2d_6_tf_tex(conv2d_6_tf_pos)), 0.0))\n#define g_27 (max(-(conv2d_6_tf1_tex(conv2d_6_tf1_pos)), 0.0))\nvec4 hook() {\n    vec4 result = mat4(0.1765669, 0.14268716, 0.19186598, 0.15799578, 0.016374417, 0.018578433, 0.0039475, 0.0046772263, 0.39840183, 0.36909792, 0.35409746, 0.37422222, -0.108508386, -0.1331279, -0.10336035, -0.14776541) * g_0;\n    result += mat4(-0.057757027, -0.14071062, -0.025283009, -0.09397916, -0.09031894, -0.14219165, -0.08299535, -0.13970287, -0.12259208, -0.14382727, -0.22002274, -0.25016093, -0.048906635, 0.06620249, 0.016965045, 0.1295978) * g_1;\n    result += mat4(-0.16748372, -0.13718611, -0.18565705, -0.15029612, -0.080749065, -0.09955825, 0.032431383, 0.023855643, -0.2748885, -0.23232168, -0.29121292, -0.26405892, 0.16556135, 0.18657646, 0.1424068, 0.18855052) * g_2;\n    result += mat4(0.10960496, 0.10851629, 0.095003806, 0.11053746, 0.09885307, 0.14437789, 0.13191165, 0.17365928, 0.16558935, 0.15473324, 0.21136154, 0.19976667, -0.07267957, -0.11469687, -0.029134216, -0.06817615) * g_3;\n    result += mat4(0.10202856, 0.04216857, -0.03959349, -0.09849683, -0.1576996, -0.049997438, -0.1579918, -0.058789205, 0.029792828, -0.07311781, -0.045432188, -0.11312683, 0.24257647, 0.16204113, 0.17869382, 0.16024388) * g_4;\n    result += mat4(0.17193612, 0.12692013, 0.13177487, 0.0796725, 0.0797928, 0.08952722, -0.012468046, 0.011071511, -0.068559825, -0.024852324, 0.0526428, 0.07917346, -0.085534215, -0.09591339, 0.04615827, 0.024577664) * g_5;\n    result += mat4(-0.14653449, -0.067267366, -0.002524394, 0.086243175, 0.13660401, 0.08039592, 0.09179008, 0.022573143, -0.024744196, 0.09120211, 0.017654825, 0.14114714, -0.16093308, -0.14538004, -0.09950235, -0.111152865) * g_6;\n    result += mat4(-0.188637, -0.12968326, -0.1200479, -0.06537649, -0.12589337, -0.106242515, -0.02788782, -0.025949068, 0.04948153, 0.02222735, -0.025291357, -0.12379292, 0.11074645, 0.11902375, -0.00056989543, -0.0024386419) * g_7;\n    result += mat4(0.018286629, 0.0072215167, 0.00037828335, 0.0047001047, 0.011478272, 0.041745186, -0.015742473, -0.002282524, -0.03440817, -0.02196847, -0.07838253, -0.07993771, -0.010155526, -0.017590692, 0.027141469, 0.029741213) * g_8;\n    result += mat4(0.016512005, 0.004950637, -0.0238836, -0.05587327, -0.03164328, -0.009499985, -0.059880238, -0.061794154, 0.023154303, -0.013266373, 0.04701534, 0.0415862, 0.06357814, 0.033057794, 0.08389772, 0.00035060212) * g_9;\n    result += mat4(-0.016403968, -0.012538788, -0.0015746636, -0.004771009, -0.021361275, -0.009695242, 0.020548422, -0.0024130535, 0.07796766, -0.01516671, 0.09961382, 0.042754963, 0.017363647, 0.03729065, -0.004795824, 0.01550197) * g_10;\n    result += mat4(-0.0028093113, 0.011869523, -0.02216933, 0.011177349, 0.033342455, -0.021146454, 0.07830085, 0.032490104, -0.03281833, 0.0060484232, -0.04081057, -0.04945058, -0.0056189033, -0.010636801, -0.041949317, -0.025739705) * g_11;\n    result += mat4(0.012979897, 0.016758928, -0.049062215, -0.0035748442, 0.0085972, 0.0036381132, -0.0055621094, 0.0041307937, -0.0008907763, -0.0034079372, -0.025680453, -0.015531803, 0.012816766, 0.009977763, -0.016416566, 0.0034859509) * g_12;\n    result += mat4(0.021753248, 0.016452711, 0.009833835, 0.0065052663, 0.0014061348, -0.046160888, -0.0132271005, -0.05051269, -0.05746351, -0.0012690664, 0.017191738, 0.018192926, -0.008879476, 0.026354216, -0.012801991, -0.029587373) * g_13;\n    result += mat4(-0.04220692, -0.0015560482, -0.0019648245, 0.013402305, -0.018259782, -0.0036008905, 0.0035650074, -0.0019178417, 0.00051580026, 0.027355857, -0.017914988, 0.004937948, -0.046335887, 0.00013612259, 0.030293299, 0.030688645) * g_14;\n    result += mat4(-0.036683388, -0.0031274238, -0.026074665, 0.021684237, 0.022639066, 0.0022493738, 0.011508554, -0.0006385944, 0.04890418, 0.020119468, 0.004167364, -0.008356099, -0.008598796, 0.0089028, -0.0029575853, 0.016687104) * g_15;\n    result += mat4(0.027207986, 0.0011099194, 0.042383645, -0.015179333, 0.014744431, 0.006148344, 0.005165422, 0.0070196544, 0.030286826, 0.016620956, -0.01611366, -0.00667594, -0.029524863, -0.024751091, -0.013321004, -0.025199674) * g_16;\n    result += mat4(0.0027477827, 0.054622147, 0.010154094, 0.025437292, 0.031773083, -0.01055473, 0.022864206, -0.029010754, -0.0029999653, 0.025018329, 0.015316208, 0.027188798, -0.10096525, -0.017268656, 0.0012529213, -0.062078856) * g_17;\n    result += mat4(-0.053670805, 0.057336535, -0.037418038, 0.06443577, -0.016027879, -0.058168363, 0.007034215, -0.03390141, -0.0019346164, -0.027947908, 0.021723913, -0.0018286633, 0.030507812, 0.018293543, 0.042917266, 0.033528328) * g_18;\n    result += mat4(-0.004559579, 0.029667616, -0.001870353, 0.0378995, -0.017147437, 0.020192018, -0.021574946, 0.031568103, 0.07487145, 0.0032376775, -0.018893708, -0.041981626, 0.054478757, 0.0061423797, 0.041280247, 0.000878061) * g_19;\n    result += mat4(0.017076394, 0.023647636, 0.029403262, 0.029923365, 0.08866472, 0.060613394, 0.1314274, -0.04490231, -0.016304834, -0.0062647443, -0.0031828512, -0.03989252, -0.024330825, 0.00741213, -0.04075287, -0.01615817) * g_20;\n    result += mat4(0.017866978, 0.017720113, -0.02846163, 0.040761847, -0.0063438355, -0.02347501, 0.029564403, -0.0029562064, 0.12505588, -0.0073986333, 0.11250363, -0.06179967, 0.07854423, 0.08546533, 0.034743227, -0.010757377) * g_21;\n    result += mat4(-0.06416677, -0.08344284, 0.030138884, 0.017635904, -0.012087523, 0.014205202, -0.03221233, -0.023834767, -0.091186255, -0.028958676, -0.04724334, 0.00013161585, 0.027391518, 0.1249978, -0.045047652, 0.10737729) * g_22;\n    result += mat4(-0.04326348, -0.03543181, -0.029558217, -0.08582413, 0.007812453, 0.014296562, -0.028779754, 0.018517692, -0.063755795, -0.036619596, -0.050809663, 0.005431336, -0.029205568, -0.011827915, -0.031110523, -0.005648626) * g_23;\n    result += mat4(0.05499293, -0.10000709, -0.0943537, 0.16143042, -0.019952895, -0.0039807972, -0.014841254, 0.0320363, -0.065173544, -0.049425576, -0.023904482, 0.03759679, -0.03207411, -0.047782745, 0.01352581, 0.008140566) * g_24;\n    result += mat4(0.055923894, -0.025134467, 0.029583648, 0.04096879, 0.027551858, -0.14995384, 0.06467113, -0.11633077, -0.01563784, -0.026909819, -0.06292879, -0.078409635, -0.009081105, -0.015533088, 0.019585673, 0.04334208) * g_25;\n    result += mat4(-0.021717606, 0.042464726, 0.02743202, -0.07388838, 0.03460472, 0.0038285658, 0.099842004, -0.098247, 0.13276267, -0.020793032, -0.008603039, -0.051913783, 0.12959045, 0.14735717, -0.10888226, -0.10263746) * g_26;\n    result += mat4(-0.16819532, 0.141579, -0.062480718, -0.021918943, 0.06348125, 0.06849444, 0.03888676, 0.027375204, -0.08194279, -0.012574497, 0.13523251, 0.13739482, -0.047547445, -0.058767617, 0.07009549, 0.028136581) * g_27;\n    result += vec4(0.069033325, 0.040207114, 0.027286075, 0.0065334598);\n    return result;\n}\n//!DESC Anime4K-v3.2-Upscale-CNN-x2-(VL)-Depth-to-Space\n//!HOOK MAIN\n//!BIND MAIN\n//!BIND conv2d_last_tf\n//!BIND conv2d_last_tf1\n//!BIND conv2d_last_tf2\n//!SAVE MAIN\n//!WIDTH conv2d_last_tf.w 2 *\n//!HEIGHT conv2d_last_tf.h 2 *\n//!WHEN OUTPUT.w MAIN.w / 1.200 > OUTPUT.h MAIN.h / 1.200 > *\nvec4 hook() {\n    vec2 f0 = fract(conv2d_last_tf_pos * conv2d_last_tf_size);\n    ivec2 i0 = ivec2(f0 * vec2(2.0));\n    float c0 = conv2d_last_tf_tex((vec2(0.5) - f0) * conv2d_last_tf_pt + conv2d_last_tf_pos)[i0.y * 2 + i0.x];\n    vec2 f1 = fract(conv2d_last_tf1_pos * conv2d_last_tf1_size);\n    ivec2 i1 = ivec2(f1 * vec2(2.0));\n    float c1 = conv2d_last_tf1_tex((vec2(0.5) - f1) * conv2d_last_tf1_pt + conv2d_last_tf1_pos)[i1.y * 2 + i1.x];\n    vec2 f2 = fract(conv2d_last_tf2_pos * conv2d_last_tf2_size);\n    ivec2 i2 = ivec2(f2 * vec2(2.0));\n    float c2 = conv2d_last_tf2_tex((vec2(0.5) - f2) * conv2d_last_tf2_pt + conv2d_last_tf2_pos)[i2.y * 2 + i2.x];\n    float c3 = c2;\n    return vec4(c0, c1, c2, c3) + MAIN_tex(MAIN_pos);\n}\n"
  },
  {
    "path": "app/src/main/java/com/yenaly/han1meviewer/Constants.kt",
    "content": "package com.yenaly.han1meviewer\n\nimport kotlinx.datetime.LocalDate\nimport kotlinx.datetime.LocalDateTime\nimport kotlinx.datetime.format.char\n\n/**\n * 我觉得空字符串写出来太逆天了，所以搞了个常量\n */\nconst val EMPTY_STRING = \"\"\n\nconst val APP_NAME = \"Han1meViewer\"\n\n// 标准时间格式\n\n/* yyyy-MM-dd */\n@JvmField\nval LOCAL_DATE_FORMAT = LocalDate.Formats.ISO\n\n/* yyyy-MM-dd HH:mm */\n@JvmField\nval LOCAL_DATE_TIME_FORMAT = LocalDateTime.Format {\n    date(LocalDate.Formats.ISO); char(' ')\n    hour(); char(':'); minute()\n}\n\n// 网络基本设置\n\nconst val USER_AGENT =\n    \"Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/103.0.0.0 Safari/537.36\"\n\n// intent傳值用名稱\n\nconst val FROM_DOWNLOAD = \"FROM_DOWNLOAD\"\n\nconst val VIDEO_CODE = \"VIDEO_CODE\"\n\n@Deprecated(\"Use [ADVANCED_SEARCH_MAP] instead\")\nconst val FROM_VIDEO_TAG = \"FROM_VIDEO_TAG\"\n\n/**\n * 接受类型 [AdvancedSearchMap] 或者 [String]\n */\nconst val ADVANCED_SEARCH_MAP = \"ADVANCED_SEARCH_MAP\"\n\nconst val COMMENT_ID = \"COMMENT_ID\"\n\nconst val COMMENT_TYPE = \"COMMENT_TYPE\"\n\nconst val DATE_CODE = \"DATE_CODE\"\n\nconst val CSRF_TOKEN = \"CSRF_TOKEN\"\n\n// Result Code\n\nconst val LOGIN_TO_MAIN_ACTIVITY = 0\n\n// 给rv传值，判断布局需要wrap_content还是match_parent，不填则为默认\n// 设置布局为MATCH_PARENT可以使rv在GridLayoutManager下能居中，反之不能\n\nconst val VIDEO_LAYOUT_WRAP_CONTENT = 1\n\nconst val VIDEO_LAYOUT_MATCH_PARENT = 2\n\n// 給CommentFragment傳值，判斷是影片評論區還是預覽評論區 [COMMENT_TYPE]\n\nconst val VIDEO_COMMENT_PREFIX = \"video\"\n\nconst val PREVIEW_COMMENT_PREFIX = \"preview\"\n\n// base url\n\n@JvmField\nval HANIME_BASE_URL = Preferences.baseUrl\n\nconst val HANIME_MAIN_HOSTNAME = \"hanime1.me\"\n\nconst val HANIME_ALTER_HOSTNAME = \"hanime1.com\"\n\nconst val HANIME_MAIN_BASE_URL = \"https://hanime1.me/\"\n\nconst val HANIME_ALTER_BASE_URL = \"https://hanime1.com/\"\n\n@JvmField\nval HANIME_LOGIN_URL = HANIME_BASE_URL + \"login\"\n\n// github url\n\nconst val HA1_GITHUB_URL = \"https://github.com/Night-stars-1/Han1meViewer\"\n\nconst val HA1_GITHUB_ISSUE_URL = \"$HA1_GITHUB_URL/issues\"\n\nconst val HA1_GITHUB_FORUM_URL = \"$HA1_GITHUB_URL/discussions\"\n\nconst val HA1_GITHUB_RELEASES_URL = \"$HA1_GITHUB_URL/releases\"\n\nconst val HA1_GITHUB_API_URL = \"https://api.github.com/repos/Night-stars-1/Han1meViewer/\"\n\n// for Shared Preference\n\nconst val LOGIN_COOKIE = \"cookie\"\n\nconst val ALREADY_LOGIN = \"already_login\"\n\n// Notification\n\nconst val DOWNLOAD_NOTIFICATION_CHANNEL = \"download_channel\"\n\nconst val UPDATE_NOTIFICATION_CHANNEL = \"update_channel\"\n\n// File\n\nconst val FILE_PROVIDER_AUTHORITY = \"${BuildConfig.APPLICATION_ID}.fileProvider\""
  },
  {
    "path": "app/src/main/java/com/yenaly/han1meviewer/FirebaseConstants.kt",
    "content": "package com.yenaly.han1meviewer\n\nobject FirebaseConstants {\n    // <editor-fold desc=\"Analytics\">\n\n    /**\n     * 高级搜索使用统计\n     */\n    const val ADV_SEARCH_OPT = \"advanced_search_options\"\n\n    /**\n     * 关键H帧使用统计\n     */\n    const val H_KEYFRAMES = \"h_keyframes\"\n\n    // </editor-fold>\n\n    // <editor-fold desc=\"Crashlytics\">\n\n    /**\n     * 当前是否为登录状态\n     */\n    const val LOGIN_STATE = \"login_state\"\n\n    /**\n     * 当前APP内使用语言\n     */\n    const val APP_LANGUAGE = \"app_language\"\n\n    /**\n     * 当前APP来源，debug、release或ci\n     */\n    const val VERSION_SOURCE = \"version_source\"\n\n    /**\n     * 当前正在下载的任务数量\n     */\n    const val RUNNING_DOWNLOAD_WORK_COUNT = \"running_download_work_count\"\n\n    // </editor-fold>\n\n    // <editor-fold desc=\"Remote Config\">\n\n    /**\n     * 是否启用 CI 更新。\n     *\n     * 由于当前使用了 Firebase，所以希望正式版更新时，减少 CI 更新的概率。\n     * 方便更好地控制更新以及崩溃数据统计。\n     */\n    const val ENABLE_CI_UPDATE = \"enable_ci_update\"\n\n    val remoteConfigDefaults: Map<String, Any> = mapOf(\n        ENABLE_CI_UPDATE to true\n    )\n\n    // </editor-fold>\n}"
  },
  {
    "path": "app/src/main/java/com/yenaly/han1meviewer/HAdvancedSearch.kt",
    "content": "package com.yenaly.han1meviewer\n\nimport java.io.Serializable\n\n/**\n * 高级搜索的枚举\n *\n * @project Han1meViewer\n * @author Yenaly Liew\n * @time 2023/09/22 022 18:13\n */\nenum class HAdvancedSearch {\n    /**\n     * 搜索关键词，类型 [String]\n     */\n    QUERY,\n\n    /**\n     * 影片类型，类型 [String]\n     */\n    GENRE,\n\n    /**\n     * 排序方式，类型 [String]\n     */\n    SORT,\n\n    /**\n     * 影片年份，类型 [Int]\n     */\n    YEAR,\n\n    /**\n     * 影片月份，类型 [Int]\n     */\n    MONTH,\n\n    /**\n     * 影片时长，类型 [String]\n     */\n    DURATION,\n\n    /**\n     * 影片 tag，类型 [String] (Deprecated) 或 HashSet&lt;String&gt; (Deprecated)\n     * 或 Map<Int, String> 或 Map<Int, HashSet&lt;String&gt;>\n     */\n    TAGS,\n\n    /**\n     * 影片品牌，类型 [String] 或 [HashSet&lt;String&gt;]\n     */\n    BRANDS\n}\n\n/**\n * 高级搜索的 Map，所有给 SearchActivity 的传参走这里！\n */\ntypealias AdvancedSearchMap = HashMap<HAdvancedSearch, Serializable>\n\n@Suppress(\"NOTHING_TO_INLINE\")\ninline fun advancedSearchMapOf(vararg pairs: Pair<HAdvancedSearch, Serializable>) =\n    hashMapOf(*pairs)"
  },
  {
    "path": "app/src/main/java/com/yenaly/han1meviewer/HCacheManager.kt",
    "content": "package com.yenaly.han1meviewer\n\nimport androidx.annotation.WorkerThread\nimport com.yenaly.han1meviewer.logic.DatabaseRepo\nimport com.yenaly.han1meviewer.logic.model.HanimeVideo\nimport com.yenaly.yenaly_libs.utils.createFileIfNotExists\nimport kotlinx.coroutines.Dispatchers\nimport kotlinx.coroutines.flow.Flow\nimport kotlinx.coroutines.flow.flow\nimport kotlinx.coroutines.flow.flowOn\nimport kotlinx.serialization.ExperimentalSerializationApi\nimport kotlinx.serialization.json.decodeFromStream\nimport kotlinx.serialization.json.encodeToStream\nimport java.io.File\n\n/**\n * @project Han1meViewer\n * @author Yenaly Liew\n * @since 2025/3/5 20:11\n */\nobject HCacheManager {\n\n    private const val CACHE_INFO_FILE = \"info.json\"\n\n    /**\n     * 保存 HanimeVideo 信息，用于下载后直接在 APP 内观看\n     */\n    @OptIn(ExperimentalSerializationApi::class)\n    @WorkerThread\n    fun saveHanimeVideoInfo(videoCode: String, info: HanimeVideo) {\n        val folder = HFileManager.getDownloadVideoFolder(videoCode)\n        val file = File(folder, CACHE_INFO_FILE)\n        file.createFileIfNotExists()\n        HJson.encodeToStream(info, file.outputStream())\n    }\n\n    /**\n     * 加载 HanimeVideo 信息，用于下载后直接在 APP 内观看\n     */\n    @OptIn(ExperimentalSerializationApi::class)\n    fun loadHanimeVideoInfo(videoCode: String): Flow<HanimeVideo?> {\n        return flow {\n            val entity = DatabaseRepo.HanimeDownload.find(videoCode)\n            if (entity != null) {\n                val folder = HFileManager.getDownloadVideoFolder(videoCode)\n                var cacheFile = File(folder, CACHE_INFO_FILE)\n                if (!cacheFile.exists()) {\n                    val folder = HFileManager.getDownloadVideoFolder(videoCode, true)\n                    cacheFile = File(folder, CACHE_INFO_FILE)\n                }\n                val info = kotlin.runCatching {\n                    if (cacheFile.exists()) HJson.decodeFromStream<HanimeVideo?>(cacheFile.inputStream()) else null\n                }.getOrNull()\n                emit(\n                    info?.copy(\n                        videoUrls = linkedMapOf(\n                            entity.quality to HanimeLink(\n                                entity.videoUri, HFileManager.DEF_VIDEO_TYPE\n                            )\n                        ),\n                        coverUrl = entity.coverUri ?: entity.coverUrl\n                    )\n                )\n            } else {\n                emit(null)\n            }\n        }.flowOn(Dispatchers.IO)\n    }\n}"
  },
  {
    "path": "app/src/main/java/com/yenaly/han1meviewer/HCrashHandler.kt",
    "content": "package com.yenaly.han1meviewer\n\nimport com.yenaly.yenaly_libs.ActivityManager\n\nobject HCrashHandler : Thread.UncaughtExceptionHandler {\n    override fun uncaughtException(t: Thread, e: Throwable) {\n        e.printStackTrace()\n        ActivityManager.restart(killProcess = true)\n    }\n}"
  },
  {
    "path": "app/src/main/java/com/yenaly/han1meviewer/HFileManager.kt",
    "content": "package com.yenaly.han1meviewer\n\nimport android.os.Environment\nimport com.yenaly.yenaly_libs.utils.applicationContext\nimport com.yenaly.yenaly_libs.utils.makeFolderNoMedia\nimport java.io.File\n\nobject HFileManager {\n\n    const val HANIME_DOWNLOAD_FOLDER = \"hanime_download\"\n\n    private val illegalCharsRegex = Regex(\"\"\"[\"*/:<>?\\\\|\\x00-\\x1F\\x7F]\"\"\")\n\n    val appExternalDownloadFolder: File\n        get() = File(\n            Environment.getExternalStoragePublicDirectory(Environment.DIRECTORY_DOWNLOADS),\n            APP_NAME\n        ).also { file -> file.makeFolderNoMedia() }\n\n    val appDownloadFolder: File\n        get() = File(\n            applicationContext.getExternalFilesDir(Environment.DIRECTORY_DOWNLOADS),\n            APP_NAME\n        ).also { file -> file.makeFolderNoMedia() }\n\n    const val DEF_VIDEO_TYPE = \"mp4\"\n    const val DEF_VIDEO_COVER_TYPE = \"png\"\n\n    fun createVideoName(\n        title: String, quality: String, suffix: String = DEF_VIDEO_TYPE\n    ) = \"${title.replaceAllIllegalChars()}_${quality}.${suffix}\"\n\n    fun createVideoCoverName(\n        title: String, suffix: String = DEF_VIDEO_COVER_TYPE\n    ) = \"${title.replaceAllIllegalChars()}.${suffix}\"\n\n    fun getDownloadFolder(again: Boolean = false): File {\n        return if (again) {\n            if (!Preferences.isPrivateDirectory) appDownloadFolder else appExternalDownloadFolder\n        } else {\n            if (Preferences.isPrivateDirectory) appDownloadFolder else appExternalDownloadFolder\n        }\n    }\n\n    fun getDownloadVideoFolder(videoCode: String, again: Boolean = false): File {\n        val appDownloadFolder = getDownloadFolder(again)\n        return File(appDownloadFolder, \"$HANIME_DOWNLOAD_FOLDER/$videoCode\")\n    }\n\n    fun getDownloadVideoFile(\n        videoCode: String, title: String, quality: String,\n        suffix: String = DEF_VIDEO_TYPE\n    ): File {\n        return File(getDownloadVideoFolder(videoCode), createVideoName(title, quality, suffix))\n    }\n\n    fun getDownloadVideoCoverFile(\n        videoCode: String, title: String,\n        suffix: String = DEF_VIDEO_COVER_TYPE\n    ): File {\n        return File(getDownloadVideoFolder(videoCode), createVideoCoverName(title, suffix))\n    }\n\n    /**\n     * Replace all illegal characters in the string with \"_\"\n     *\n     * 若文件名中有非法字符，则替换为\"_\"，以避免文件名错误\n     */\n    private fun String.replaceAllIllegalChars(): String {\n        return illegalCharsRegex.replace(this, \"_\")\n    }\n}"
  },
  {
    "path": "app/src/main/java/com/yenaly/han1meviewer/HInitializer.kt",
    "content": "package com.yenaly.han1meviewer\n\nimport android.content.Context\nimport com.yenaly.yenaly_libs.base.YenalyInitializer\n\nclass HInitializer : YenalyInitializer() {\n    override fun create(context: Context) {\n        super.create(context)\n        // 用于处理 Firebase Crashlytics 初始化\n        Thread.setDefaultUncaughtExceptionHandler(HCrashHandler)\n    }\n}"
  },
  {
    "path": "app/src/main/java/com/yenaly/han1meviewer/HanimeApplication.kt",
    "content": "package com.yenaly.han1meviewer\n\nimport android.util.Log\nimport androidx.core.app.NotificationChannelCompat\nimport androidx.core.app.NotificationManagerCompat\nimport com.google.android.material.color.DynamicColors\nimport com.google.firebase.Firebase\nimport com.google.firebase.analytics.analytics\nimport com.google.firebase.crashlytics.crashlytics\nimport com.google.firebase.crashlytics.setCustomKeys\nimport com.google.firebase.remoteconfig.remoteConfig\nimport com.google.firebase.remoteconfig.remoteConfigSettings\nimport com.scwang.smart.refresh.footer.ClassicsFooter\nimport com.scwang.smart.refresh.header.MaterialHeader\nimport com.scwang.smart.refresh.layout.SmartRefreshLayout\nimport com.yenaly.han1meviewer.logic.network.HProxySelector\nimport com.yenaly.han1meviewer.ui.viewmodel.AppViewModel\nimport com.yenaly.han1meviewer.util.AnimeShaders\nimport com.yenaly.yenaly_libs.base.YenalyApplication\nimport com.yenaly.yenaly_libs.utils.LanguageHelper\nimport `is`.xyz.mpv.MPVLib\n\n/**\n * @project Hanime1\n * @author Yenaly Liew\n * @time 2022/06/08 008 17:32\n */\nclass HanimeApplication : YenalyApplication() {\n\n    companion object {\n        const val TAG = \"HanimeApplication\"\n\n        init {\n            SmartRefreshLayout.setDefaultRefreshHeaderCreator { context, _ ->\n                return@setDefaultRefreshHeaderCreator MaterialHeader(context)\n            }\n            SmartRefreshLayout.setDefaultRefreshFooterCreator { context, _ ->\n                return@setDefaultRefreshFooterCreator ClassicsFooter(context)\n            }\n        }\n    }\n\n    /**\n     * 已经在 [HInitializer] 中处理了\n     */\n    override val isDefaultCrashHandlerEnabled: Boolean = false\n\n    override fun onCreate() {\n        super.onCreate()\n        DynamicColors.applyToActivitiesIfAvailable(this)\n        HProxySelector.rebuildNetwork()\n\n        initFirebase()\n        initNotificationChannel()\n\n        MPVLib.create(applicationContext)\n        MPVLib.init()\n\n        if (AnimeShaders.copyShaderAssets(applicationContext) <= 0) {\n            Log.w(TAG, \"Shader 复制失败\")\n        }\n    }\n\n    private fun initFirebase() {\n        // 用于处理 Firebase Analytics 初始化\n        Firebase.analytics.setAnalyticsCollectionEnabled(Preferences.isAnalyticsEnabled)\n        // 用于处理 Firebase Crashlytics 初始化\n        Firebase.crashlytics.apply {\n            isCrashlyticsCollectionEnabled = !BuildConfig.DEBUG\n            setCustomKeys {\n                key(\n                    FirebaseConstants.APP_LANGUAGE,\n                    LanguageHelper.preferredLanguage.toLanguageTag()\n                )\n                key(\n                    FirebaseConstants.VERSION_SOURCE,\n                    BuildConfig.HA1_VERSION_SOURCE\n                )\n            }\n        }\n        // 用于处理 Firebase Remote Config 初始化\n        Firebase.remoteConfig.apply {\n            setConfigSettingsAsync(remoteConfigSettings {\n                minimumFetchIntervalInSeconds = if (BuildConfig.DEBUG) 0 else 3 * 60 * 60\n                fetchTimeoutInSeconds = 10\n            })\n            setDefaultsAsync(FirebaseConstants.remoteConfigDefaults)\n            fetchAndActivate().addOnCompleteListener {\n                AppViewModel.getLatestVersion(delayMillis = 200)\n            }\n        }\n    }\n\n    private fun initNotificationChannel() {\n        val nm = NotificationManagerCompat.from(this)\n\n        val hanimeDownloadChannel = NotificationChannelCompat.Builder(\n            DOWNLOAD_NOTIFICATION_CHANNEL,\n            NotificationManagerCompat.IMPORTANCE_HIGH\n        ).setName(\"Hanime Download\").build()\n        nm.createNotificationChannel(hanimeDownloadChannel)\n\n        val appUpdateChannel = NotificationChannelCompat.Builder(\n            UPDATE_NOTIFICATION_CHANNEL,\n            NotificationManagerCompat.IMPORTANCE_HIGH\n        ).setName(\"App Update\").build()\n        nm.createNotificationChannel(appUpdateChannel)\n    }\n}"
  },
  {
    "path": "app/src/main/java/com/yenaly/han1meviewer/HanimeManager.kt",
    "content": "package com.yenaly.han1meviewer\n\nimport android.webkit.CookieManager\nimport androidx.core.text.parseAsHtml\nimport com.yenaly.han1meviewer.Preferences.isAlreadyLogin\nimport com.yenaly.han1meviewer.Preferences.loginCookie\nimport com.yenaly.han1meviewer.logic.network.HCookieJar\nimport com.yenaly.han1meviewer.util.CookieString\nimport kotlinx.serialization.json.Json\n\n@JvmField\nval HJson = Json {\n    ignoreUnknownKeys = true\n}\n\n/**\n * 给用户显示的错误信息\n *\n * ぴえん化\n */\nval Throwable.pienization: CharSequence get() = \"🥺\\n$localizedMessage\"\n\n// base\n\nprivate const val HANIME_TITLE_HTML =\n    \"\"\"<span style=\"color: #FF0000;\"><b>H</b></span><b>an1me</b>Viewer\"\"\"\n\nval hanimeSpannedTitle = HANIME_TITLE_HTML.parseAsHtml()\n\n/**\n * 獲取 Hanime 影片地址\n */\nfun getHanimeVideoLink(videoCode: String) = HANIME_BASE_URL + \"watch?v=\" + videoCode\n\n/**\n * 獲取 Hanime 影片分享文本\n */\nfun getHanimeShareText(title: String, videoCode: String): String = buildString {\n    appendLine(title)\n    appendLine(getHanimeVideoLink(videoCode))\n    append(\"- From Han1meViewer -\")\n}\n\n/**\n * 獲取 Hanime 影片**官方**下載地址\n */\nfun getHanimeVideoDownloadLink(videoCode: String) =\n    HANIME_BASE_URL + \"download?v=\" + videoCode\n\nval videoUrlRegex = Regex(\"\"\"hanime1\\.(?:com|me)/watch\\?v=(\\d+)\"\"\")\n\nfun String.toVideoCode() = videoUrlRegex.find(this)?.groupValues?.get(1)\n\n// log in and log out\n\nfun logout() {\n    isAlreadyLogin = false\n    loginCookie = CookieString(EMPTY_STRING)\n    HCookieJar.cookieMap.clear()\n    CookieManager.getInstance().removeAllCookies(null)\n}\n\nfun login(cookies: String) {\n    isAlreadyLogin = true\n    loginCookie = CookieString(cookies)\n}\n\nfun login(cookies: List<String>) {\n    login(cookies.joinToString(\";\") {\n        it.substringBefore(';')\n    })\n}"
  },
  {
    "path": "app/src/main/java/com/yenaly/han1meviewer/HanimeResolution.kt",
    "content": "package com.yenaly.han1meviewer\n\nimport kotlinx.serialization.Serializable\nimport okhttp3.MediaType.Companion.toMediaTypeOrNull\n\n/**\n * resolution to link map\n */\ntypealias ResolutionLinkMap = LinkedHashMap<String, HanimeLink>\n\n/**\n * 如果你在其他地方看到了 Quality，那就是 Resolution，我混用了。\n *\n * @project Han1meViewer\n * @author Yenaly Liew\n * @time 2022/10/11 011 21:19\n */\nclass HanimeResolution {\n\n    private val resArray = arrayOfNulls<Pair<String, HanimeLink>>(5)\n\n    companion object {\n\n        // 目前hanime1有的分辨率好像就這些，暫時不考慮其他分辨率\n\n        const val RES_1080P = \"1080P\"\n        const val RES_720P = \"720P\"\n        const val RES_480P = \"480P\"\n        const val RES_240P = \"240P\"\n        const val RES_UNKNOWN = \"Unknown\"\n    }\n\n    /**\n     * 解析分辨率，從高到低排列。\n     *\n     * @param resString 分辨率\n     * @param resLink 分辨率對應網址\n     * @param type 例如 video/mp4\n     */\n    fun parseResolution(resString: String?, resLink: String, type: String? = null) {\n        val mediaType = type?.toMediaTypeOrNull()?.takeIf {\n            it.type.equals(\"video\", ignoreCase = true)\n        }\n        val link = HanimeLink(resLink, mediaType?.subtype)\n        when (resString) {\n            RES_1080P -> resArray[0] = RES_1080P to link\n            RES_720P -> resArray[1] = RES_720P to link\n            RES_480P -> resArray[2] = RES_480P to link\n            RES_240P -> resArray[3] = RES_240P to link\n            null -> resArray[4] = RES_UNKNOWN to link\n        }\n    }\n\n    fun toResolutionLinkMap(): ResolutionLinkMap {\n        return resArray.filterNotNull().toMap(linkedMapOf())\n    }\n}\n\n@Serializable\ndata class HanimeLink(\n    val link: String,\n    val subtype: String?,\n) {\n    val suffix: String\n        get() = when (subtype?.lowercase()) {\n            \"mp4\" -> \"mp4\"\n            \"mpeg\" -> \"mpeg\"\n            \"x-msvideo\" -> \"avi\"\n            \"3gpp\" -> \"3gp\"\n            \"3gpp2\" -> \"3g2\"\n            \"ogg\" -> \"ogv\"\n            \"mp2t\" -> \"ts\"\n            \"webm\" -> \"webm\"\n            else -> HFileManager.DEF_VIDEO_TYPE\n        }\n}"
  },
  {
    "path": "app/src/main/java/com/yenaly/han1meviewer/Preferences.kt",
    "content": "package com.yenaly.han1meviewer\n\nimport android.content.SharedPreferences\nimport android.os.Build\nimport androidx.preference.PreferenceManager\nimport com.yenaly.han1meviewer.logic.network.HProxySelector\nimport com.yenaly.han1meviewer.logic.network.interceptor.SpeedLimitInterceptor\nimport com.yenaly.han1meviewer.ui.fragment.settings.DownloadSettingsFragment\nimport com.yenaly.han1meviewer.ui.fragment.settings.HKeyframeSettingsFragment\nimport com.yenaly.han1meviewer.ui.fragment.settings.HomeSettingsFragment\nimport com.yenaly.han1meviewer.ui.fragment.settings.NetworkSettingsFragment\nimport com.yenaly.han1meviewer.ui.fragment.settings.PlayerSettingsFragment\nimport com.yenaly.han1meviewer.ui.view.video.HJzvdStd\nimport com.yenaly.han1meviewer.ui.view.video.HMediaKernel\nimport com.yenaly.han1meviewer.util.CookieString\nimport com.yenaly.han1meviewer.worker.HanimeDownloadManagerV2\nimport com.yenaly.yenaly_libs.utils.applicationContext\nimport com.yenaly.yenaly_libs.utils.getSpValue\nimport com.yenaly.yenaly_libs.utils.putSpValue\nimport kotlinx.coroutines.flow.MutableStateFlow\nimport kotlinx.datetime.Clock\nimport kotlinx.datetime.Instant\nimport kotlin.time.Duration.Companion.days\n\nobject Preferences {\n    /**\n     * [Preference][androidx.preference.PreferenceFragmentCompat]自帶的SP\n     */\n    val preferenceSp: SharedPreferences\n        get() = PreferenceManager.getDefaultSharedPreferences(\n            applicationContext\n        )\n\n    // app 相關\n\n    /**\n     * 是否登入，一般跟[loginCookie]一起賦值\n     */\n    var isAlreadyLogin: Boolean\n        get() = getSpValue(ALREADY_LOGIN, false)\n        set(value) {\n            loginStateFlow.value = value\n            putSpValue(ALREADY_LOGIN, value)\n        }\n\n    val loginStateFlow = MutableStateFlow(isAlreadyLogin)\n\n    /**\n     * 保存的string格式的登入cookie\n     */\n    var loginCookie\n        get() = CookieString(getSpValue(LOGIN_COOKIE, EMPTY_STRING))\n        set(value) {\n            loginCookieStateFlow.value = value\n            putSpValue(LOGIN_COOKIE, value.cookie)\n        }\n\n    val loginCookieStateFlow = MutableStateFlow(loginCookie)\n\n    // 更新 相關\n\n    private const val UPDATE_NODE_ID = \"update_node_id\"\n\n    var updateNodeId: String\n        get() = getSpValue(UPDATE_NODE_ID, EMPTY_STRING)\n        set(value) = putSpValue(UPDATE_NODE_ID, value)\n\n    var lastUpdatePopupTime\n        get() = getSpValue(HomeSettingsFragment.LAST_UPDATE_POPUP_TIME, 0L)\n        set(value) = putSpValue(HomeSettingsFragment.LAST_UPDATE_POPUP_TIME, value)\n\n    val updatePopupIntervalDays\n        get() = preferenceSp.getInt(HomeSettingsFragment.UPDATE_POPUP_INTERVAL_DAYS, 0)\n\n    val useCIUpdateChannel\n        get() = preferenceSp.getBoolean(HomeSettingsFragment.USE_CI_UPDATE_CHANNEL, false)\n\n    // Check if show update dialog.\n    val isUpdateDialogVisible: Boolean\n        get() {\n            val now = Clock.System.now()\n            val lastCheckTime = Instant.fromEpochSeconds(lastUpdatePopupTime)\n            val interval = updatePopupIntervalDays\n            return now > lastCheckTime + interval.days\n        }\n\n    // 設定 相關\n\n    val switchPlayerKernel: String\n        get() = preferenceSp.getString(\n            PlayerSettingsFragment.SWITCH_PLAYER_KERNEL,\n            HMediaKernel.Type.ExoPlayer.name\n        ) ?: HMediaKernel.Type.ExoPlayer.name\n\n    val showBottomProgress: Boolean\n        get() = preferenceSp.getBoolean(\n            PlayerSettingsFragment.SHOW_BOTTOM_PROGRESS,\n            true\n        )\n\n    val playerSpeed: Float\n        get() = preferenceSp.getString(\n            PlayerSettingsFragment.PLAYER_SPEED,\n            HJzvdStd.DEF_SPEED.toString()\n        )?.toFloat() ?: HJzvdStd.DEF_SPEED\n\n    val slideSensitivity: Int\n        get() = preferenceSp.getInt(\n            PlayerSettingsFragment.SLIDE_SENSITIVITY,\n            HJzvdStd.DEF_PROGRESS_SLIDE_SENSITIVITY\n        )\n\n    val longPressSpeedTime: Float\n        get() = preferenceSp.getString(\n            PlayerSettingsFragment.LONG_PRESS_SPEED_TIMES,\n            HJzvdStd.DEF_LONG_PRESS_SPEED_TIMES.toString()\n        )?.toFloat() ?: HJzvdStd.DEF_LONG_PRESS_SPEED_TIMES\n\n    val videoLanguage: String\n        get() = preferenceSp.getString(HomeSettingsFragment.VIDEO_LANGUAGE, \"zh-CHT\") ?: \"zh-CHT\"\n\n    val baseUrl: String\n        get() = preferenceSp.getString(NetworkSettingsFragment.DOMAIN_NAME, HANIME_MAIN_BASE_URL)\n            ?: HANIME_MAIN_BASE_URL\n\n    val useBuiltInHosts: Boolean\n        get() = preferenceSp.getBoolean(NetworkSettingsFragment.USE_BUILT_IN_HOSTS, false)\n\n    val isPingTest: Boolean\n        get() = preferenceSp.getBoolean(NetworkSettingsFragment.PING_TEST, false)\n\n    val isPipAllowed: Boolean\n        get() = preferenceSp.getBoolean(HomeSettingsFragment.ALLOW_PIP, false) && Build.VERSION.SDK_INT >= Build.VERSION_CODES.O\n\n    // 關鍵H幀 相關\n\n    val whenCountdownRemind: Int\n        get() = preferenceSp.getInt(\n            HKeyframeSettingsFragment.WHEN_COUNTDOWN_REMIND,\n            HJzvdStd.DEF_COUNTDOWN_SEC\n        ) * 1_000 // 越不了界，最大就30_000ms而已\n\n    val showCommentWhenCountdown: Boolean\n        get() = preferenceSp.getBoolean(\n            HKeyframeSettingsFragment.SHOW_COMMENT_WHEN_COUNTDOWN,\n            false\n        )\n\n    val hKeyframesEnable: Boolean\n        get() = preferenceSp.getBoolean(\n            HKeyframeSettingsFragment.H_KEYFRAMES_ENABLE,\n            true\n        )\n\n    val sharedHKeyframesEnable: Boolean\n        get() = preferenceSp.getBoolean(\n            HKeyframeSettingsFragment.SHARED_H_KEYFRAMES_ENABLE,\n            true\n        )\n\n    val sharedHKeyframesUseFirst: Boolean\n        get() = preferenceSp.getBoolean(\n            HKeyframeSettingsFragment.SHARED_H_KEYFRAMES_USE_FIRST,\n            false\n        )\n\n    // 代理 相關\n\n    val proxyType: Int\n        get() = preferenceSp.getInt(\n            NetworkSettingsFragment.PROXY_TYPE,\n            HProxySelector.TYPE_SYSTEM\n        )\n\n    val proxyIp: String\n        get() = preferenceSp.getString(NetworkSettingsFragment.PROXY_IP, EMPTY_STRING).orEmpty()\n\n    val proxyPort: Int\n        get() = preferenceSp.getInt(NetworkSettingsFragment.PROXY_PORT, -1)\n\n    // 隐私 相關\n\n    val isAnalyticsEnabled: Boolean\n        get() = preferenceSp.getBoolean(HomeSettingsFragment.USE_ANALYTICS, true)\n\n    // 下载 相關\n\n    val downloadCountLimit: Int\n        get() = preferenceSp.getInt(\n            DownloadSettingsFragment.DOWNLOAD_COUNT_LIMIT,\n            // HanimeDownloadManager.MAX_CONCURRENT_DOWNLOAD_DEF\n            HanimeDownloadManagerV2.MAX_CONCURRENT_DOWNLOAD_DEF\n        )\n\n    /**\n     * 对应关系详见 [SpeedLimitInterceptor.SPEED_BYTES]\n     */\n    val downloadSpeedLimit: Long\n        get() {\n            val index = preferenceSp.getInt(\n                DownloadSettingsFragment.DOWNLOAD_SPEED_LIMIT,\n                SpeedLimitInterceptor.NO_LIMIT_INDEX\n            )\n            return SpeedLimitInterceptor.SPEED_BYTES[index]\n        }\n\n    val isPrivateDirectory: Boolean\n        get() = preferenceSp.getBoolean(\n            DownloadSettingsFragment.USE_PRIVATE_DIRECTORY,\n            false\n        )\n}"
  },
  {
    "path": "app/src/main/java/com/yenaly/han1meviewer/VideoCoverSize.kt",
    "content": "package com.yenaly.han1meviewer\n\nimport android.annotation.SuppressLint\nimport android.view.ViewGroup\nimport androidx.core.view.updateLayoutParams\nimport com.yenaly.yenaly_libs.utils.applicationContext\nimport com.yenaly.yenaly_libs.utils.dp\n\n/**\n * 用于计算视频封面的大小动态调整！\n *\n * @project Han1meViewer\n * @author Yenaly Liew\n * @time 2024/04/08 008 21:54\n */\n@SuppressLint(\"StaticFieldLeak\")\nobject VideoCoverSize {\n\n    private const val TAG = \"VideoCoverSize\"\n\n    private val context = applicationContext\n\n    private val screenWidth get() = context.resources.displayMetrics.widthPixels\n\n    private val videoCoverWidth =\n        context.resources.getDimension(R.dimen.video_cover_width)\n    private val simplifiedVideoCoverWidth =\n        context.resources.getDimension(R.dimen.video_cover_simplified_width)\n\n    // Ratio of the video cover's width to its height\n    private const val RATIO = 15 / 22.0\n\n    /**\n     * 自带的一个Margin\n     */\n    private val margin = 4.dp\n\n    /**\n     * 通常父View也会有一个Margin，所以这里也加上\n     */\n    private val parentMargin = 8.dp\n\n    object Normal {\n\n        /**\n         * 最少显示几个视频\n         */\n        private const val AT_LEAST = 2\n\n        val videoInOneLine\n            get() = (screenWidth / videoCoverWidth).toInt().coerceAtLeast(AT_LEAST)\n\n        fun ViewGroup.resizeForVideoCover(atLeast: Int = AT_LEAST) {\n            require(atLeast > 0)\n            val screenWidth = screenWidth\n            val videoInOneLine = (screenWidth / videoCoverWidth).toInt()\n            if (videoInOneLine < atLeast) {\n                val width = (screenWidth - parentMargin * 2) / atLeast - margin * 2\n                val height = (width * RATIO).toInt()\n                updateLayoutParams {\n                    this.width = width\n                    this.height = height\n                }\n            }\n        }\n    }\n\n    object Simplified {\n\n        /**\n         * 最少显示几个视频\n         */\n        private const val AT_LEAST = 3\n\n        val videoInOneLine\n            get() = (screenWidth / simplifiedVideoCoverWidth).toInt().coerceAtLeast(AT_LEAST)\n\n        fun ViewGroup.resizeForVideoCover(atLeast: Int = AT_LEAST) {\n            require(atLeast > 0)\n            val screenWidth = screenWidth\n            val videoInOneLine = (screenWidth / simplifiedVideoCoverWidth).toInt()\n            if (videoInOneLine < atLeast) {\n                val width = (screenWidth - parentMargin * 2) / atLeast - margin * 2\n                val height = (width / RATIO).toInt()\n                updateLayoutParams {\n                    this.width = width\n                    this.height = height\n                }\n            }\n        }\n    }\n}"
  },
  {
    "path": "app/src/main/java/com/yenaly/han1meviewer/logic/DatabaseRepo.kt",
    "content": "package com.yenaly.han1meviewer.logic\n\nimport com.yenaly.han1meviewer.Preferences\nimport com.yenaly.han1meviewer.logic.dao.DownloadDatabase\nimport com.yenaly.han1meviewer.logic.dao.HistoryDatabase\nimport com.yenaly.han1meviewer.logic.dao.MiscellanyDatabase\nimport com.yenaly.han1meviewer.logic.entity.HKeyframeEntity\nimport com.yenaly.han1meviewer.logic.entity.HKeyframeHeader\nimport com.yenaly.han1meviewer.logic.entity.HKeyframeType\nimport com.yenaly.han1meviewer.logic.entity.SearchHistoryEntity\nimport com.yenaly.han1meviewer.logic.entity.WatchHistoryEntity\nimport com.yenaly.han1meviewer.logic.entity.download.HanimeDownloadEntity\nimport com.yenaly.yenaly_libs.utils.applicationContext\nimport kotlinx.coroutines.flow.Flow\nimport kotlinx.coroutines.flow.catch\nimport kotlinx.coroutines.flow.flow\nimport kotlinx.serialization.ExperimentalSerializationApi\nimport kotlinx.serialization.json.Json\nimport kotlinx.serialization.json.decodeFromStream\n\n/**\n * @project Hanime1\n * @author Yenaly Liew\n * @time 2022/06/22 022 23:00\n */\nobject DatabaseRepo {\n\n    object HKeyframe {\n        private val hKeyframeDao = MiscellanyDatabase.instance.hKeyframeDao\n\n        fun loadAll(keyword: String? = null) =\n            if (keyword != null) hKeyframeDao.loadAll(keyword)\n            else hKeyframeDao.loadAll()\n\n        // #issue-106: 剧集分类\n        @OptIn(ExperimentalSerializationApi::class)\n        fun loadAllShared(): Flow<List<HKeyframeType>> = flow {\n            val res = applicationContext.assets.let { assets ->\n                assets.list(\"h_keyframes\")?.asSequence()\n                    ?.filter { it.endsWith(\".json\") }\n                    ?.mapNotNull { fileName ->\n                        try {\n                            assets.open(\"h_keyframes/$fileName\").use { inputStream ->\n                                Json.decodeFromStream<HKeyframeEntity>(inputStream)\n                            }\n                        } catch (e: Exception) {\n                            e.printStackTrace()\n                            null\n                        }\n                    }\n                    ?.sortedWith(\n                        compareBy<HKeyframeEntity> { it.group }.thenBy { it.episode }\n                    )\n                    ?.groupBy { it.group ?: \"???\" }\n                    ?.flatMap { (group, entities) ->\n                        listOf(HKeyframeHeader(title = group, attached = entities)) + entities\n                    }\n                    .orEmpty()\n            }\n            emit(res)\n        }\n\n        suspend fun findBy(videoCode: String) =\n            hKeyframeDao.findBy(videoCode)\n\n        @OptIn(ExperimentalSerializationApi::class)\n        fun observe(videoCode: String): Flow<HKeyframeEntity?> {\n            if (Preferences.sharedHKeyframesEnable) {\n                return flow t@{\n                    val find = hKeyframeDao.findBy(videoCode)\n                    if (find == null || Preferences.sharedHKeyframesUseFirst) {\n                        applicationContext.assets\n                            .open(\"h_keyframes/$videoCode.json\")\n                            .use { inputStream ->\n                                val entity = Json.decodeFromStream<HKeyframeEntity>(inputStream)\n                                this@t.emit(entity)\n                            }\n                    } else {\n                        hKeyframeDao.observe(videoCode).collect {\n                            this@t.emit(it)\n                        }\n                    }\n                }.catch t@{ e ->\n                    e.printStackTrace()\n                    hKeyframeDao.observe(videoCode).collect {\n                        this@t.emit(it)\n                    }\n                }\n            }\n            return hKeyframeDao.observe(videoCode)\n        }\n\n        suspend fun insert(entity: HKeyframeEntity) = hKeyframeDao.insert(entity)\n\n        suspend fun update(entity: HKeyframeEntity) = hKeyframeDao.update(entity)\n\n        suspend fun delete(entity: HKeyframeEntity) =\n            hKeyframeDao.delete(entity)\n\n        suspend fun modifyKeyframe(\n            videoCode: String,\n            oldKeyframe: HKeyframeEntity.Keyframe, keyframe: HKeyframeEntity.Keyframe,\n        ) = hKeyframeDao.modifyKeyframe(videoCode, oldKeyframe, keyframe)\n\n        suspend fun appendKeyframe(\n            videoCode: String, title: String,\n            keyframe: HKeyframeEntity.Keyframe,\n        ) = hKeyframeDao.appendKeyframe(videoCode, title, keyframe)\n\n        suspend fun removeKeyframe(\n            videoCode: String,\n            keyframe: HKeyframeEntity.Keyframe,\n        ) = hKeyframeDao.removeKeyframe(videoCode, keyframe)\n    }\n\n    object SearchHistory {\n        private val searchHistoryDao = HistoryDatabase.instance.searchHistory\n\n        @JvmOverloads\n        fun loadAll(keyword: String? = null) =\n            if (keyword.isNullOrBlank()) searchHistoryDao.loadAll()\n            else searchHistoryDao.loadAll(keyword)\n\n        suspend fun delete(history: SearchHistoryEntity) =\n            searchHistoryDao.delete(history)\n\n        suspend fun insert(history: SearchHistoryEntity) =\n            searchHistoryDao.insertOrUpdate(history)\n\n        suspend fun deleteByKeyword(query: String) =\n            searchHistoryDao.deleteByKeyword(query)\n    }\n\n    object WatchHistory {\n        private val watchHistoryDao = HistoryDatabase.instance.watchHistory\n\n        fun loadAll() =\n            watchHistoryDao.loadAll()\n\n        suspend fun delete(history: WatchHistoryEntity) =\n            watchHistoryDao.delete(history)\n\n        suspend fun deleteAll() =\n            watchHistoryDao.deleteAll()\n\n        suspend fun update(history: WatchHistoryEntity) =\n            watchHistoryDao.update(history)\n\n        suspend fun insert(history: WatchHistoryEntity) =\n            watchHistoryDao.insertOrUpdate(history)\n    }\n\n    object HanimeDownload {\n        private val hanimeDownloadDao = DownloadDatabase.instance.hanimeDownloadDao\n        private val hanimeUpdateDao = DownloadDatabase.instance.hUpdateDao\n\n        fun loadAllDownloadingHanime() =\n            hanimeDownloadDao.loadAllDownloadingHanime()\n\n        fun loadloadUpdating() =\n            hanimeUpdateDao.loadUpdating()\n\n        /**\n         * 查询所有视频，并且每个视频要有当前他在的分类\n         */\n        fun loadAllDownloadedHanime(\n            sortedBy: HanimeDownloadEntity.SortedBy,\n            ascending: Boolean,\n        ) = when (sortedBy) {\n            HanimeDownloadEntity.SortedBy.TITLE ->\n                hanimeDownloadDao.loadAllDownloadedHanimeByTitle(ascending)\n\n            HanimeDownloadEntity.SortedBy.ID ->\n                hanimeDownloadDao.loadAllDownloadedHanimeById(ascending)\n        }\n\n        suspend fun delete(videoCode: String, quality: String) =\n            hanimeDownloadDao.delete(videoCode, quality)\n\n        suspend fun delete(videoCode: String) =\n            hanimeDownloadDao.delete(videoCode)\n\n        suspend fun pauseAll() =\n            hanimeDownloadDao.pauseAll()\n\n        suspend fun delete(entity: HanimeDownloadEntity) =\n            hanimeDownloadDao.delete(entity)\n\n        suspend fun insert(entity: HanimeDownloadEntity) =\n            hanimeDownloadDao.insert(entity)\n\n        suspend fun update(entity: HanimeDownloadEntity) =\n            hanimeDownloadDao.update(entity)\n\n        suspend fun find(videoCode: String, quality: String) =\n            hanimeDownloadDao.find(videoCode, quality)\n\n        suspend fun find(videoCode: String) =\n            hanimeDownloadDao.find(videoCode)\n\n        @Deprecated(\"查屁\")\n        suspend fun countBy(videoCode: String) =\n            hanimeDownloadDao.countBy(videoCode)\n    }\n}"
  },
  {
    "path": "app/src/main/java/com/yenaly/han1meviewer/logic/NetworkRepo.kt",
    "content": "package com.yenaly.han1meviewer.logic\n\nimport android.util.Log\nimport com.yenaly.han1meviewer.EMPTY_STRING\nimport com.yenaly.han1meviewer.Preferences.isAlreadyLogin\nimport com.yenaly.han1meviewer.R\nimport com.yenaly.han1meviewer.logic.exception.CloudFlareBlockedException\nimport com.yenaly.han1meviewer.logic.exception.HanimeNotFoundException\nimport com.yenaly.han1meviewer.logic.exception.IPBlockedException\nimport com.yenaly.han1meviewer.logic.exception.ParseException\nimport com.yenaly.han1meviewer.logic.model.CommentPlace\nimport com.yenaly.han1meviewer.logic.model.ModifiedPlaylistArgs\nimport com.yenaly.han1meviewer.logic.model.MyListType\nimport com.yenaly.han1meviewer.logic.model.VideoCommentArgs\nimport com.yenaly.han1meviewer.logic.model.VideoComments\nimport com.yenaly.han1meviewer.logic.network.HUpdater\nimport com.yenaly.han1meviewer.logic.network.HanimeNetwork\nimport com.yenaly.han1meviewer.logic.state.PageLoadingState\nimport com.yenaly.han1meviewer.logic.state.VideoLoadingState\nimport com.yenaly.han1meviewer.logic.state.WebsiteState\nimport com.yenaly.yenaly_libs.utils.applicationContext\nimport kotlinx.coroutines.CancellationException\nimport kotlinx.coroutines.Dispatchers\nimport kotlinx.coroutines.flow.catch\nimport kotlinx.coroutines.flow.flow\nimport kotlinx.coroutines.flow.flowOn\nimport okhttp3.ResponseBody\nimport org.json.JSONObject\nimport retrofit2.Response\nimport javax.net.ssl.SSLHandshakeException\n\n/**\n * @project Hanime1\n * @author Yenaly Liew\n * @time 2022/06/08 008 22:38\n */\nobject NetworkRepo {\n\n    //<editor-fold desc=\"Hanime\">\n\n    fun getHomePage() = websiteIOFlow(\n        request = { HanimeNetwork.hanimeService.getHomePage() },\n        action = Parser::homePageVer2\n    )\n\n    fun getHanimeSearchResult(\n        page: Int, query: String?, genre: String?,\n        sort: String?, broad: Boolean, date: String?,\n        duration: String?, tags: Set<String>, brands: Set<String>,\n    ) = pageIOFlow(\n        request = {\n            HanimeNetwork.hanimeService.getHanimeSearchResult(\n                page, query, genre, sort,\n                if (broad) \"on\" else null,\n                date, duration, tags, brands\n            )\n        },\n        action = Parser::hanimeSearch\n    )\n\n    fun getHanimeVideo(videoCode: String) = videoIOFlow(\n        request = { HanimeNetwork.hanimeService.getHanimeVideo(videoCode) },\n        action = Parser::hanimeVideoVer2\n    )\n\n    fun getHanimePreview(date: String) = websiteIOFlow(\n        request = { HanimeNetwork.hanimeService.getHanimePreview(date) },\n        action = Parser::hanimePreview\n    )\n\n    //</editor-fold>\n\n    //<editor-fold desc=\"My List\">\n\n    fun getMyListItems(page: Int, typeOrCode: Any) = pageIOFlow(\n        request = {\n            when (typeOrCode) {\n                is String ->\n                    HanimeNetwork.myListService.getMyListItems(page, typeOrCode)\n\n                is MyListType ->\n                    HanimeNetwork.myListService.getMyListItems(page, typeOrCode.value)\n\n                else ->\n                    throw IllegalArgumentException(\"typeOrId must be String or MyListType\")\n            }\n        },\n        action = Parser::myListItems\n    )\n\n    fun getSubscriptions(page: Int) = pageIOFlow(\n        request = {\n            HanimeNetwork.myListService.getMyListItems(page, MyListType.SUBSCRIPTION.value)\n        },\n        action = Parser::subscriptionItems\n    )\n\n    fun deleteMyListItems(\n        typeOrCode: Any,\n        videoCode: String,\n        position: Int,\n        token: String?,\n    ) = websiteIOFlow(\n        request = {\n            when (typeOrCode) {\n                is String ->\n                    HanimeNetwork.myListService.deleteMyListItems(\n                        typeOrCode, videoCode,\n                        csrfToken = token\n                    )\n\n                is MyListType ->\n                    HanimeNetwork.myListService.deleteMyListItems(\n                        typeOrCode.value, videoCode,\n                        csrfToken = token\n                    )\n\n                else ->\n                    throw IllegalArgumentException(\"typeOrId must be String or MyListType\")\n            }\n        }\n    ) { deleteBody ->\n        val jsonObject = JSONObject(deleteBody)\n        val returnVideoCode = jsonObject.get(\"video_id\").toString()\n        if (videoCode == returnVideoCode) {\n            return@websiteIOFlow WebsiteState.Success(position)\n        }\n\n        return@websiteIOFlow WebsiteState.Error(IllegalStateException(\"cannot delete it ?!\"))\n    }\n\n    fun getPlaylists() = websiteIOFlow(\n        request = HanimeNetwork.myListService::getPlaylists,\n        action = Parser::playlists\n    )\n\n    fun addToMyFavVideo(\n        videoCode: String,\n        likeStatus: Boolean, // false => \"\": add fav; true => \"1\": cancel fav;\n        currentUserId: String?,\n        token: String?,\n    ) = websiteIOFlow(\n        request = {\n            HanimeNetwork.myListService.addToMyFavVideo(\n                videoCode, if (likeStatus) \"1\" else EMPTY_STRING,\n                token, currentUserId\n            )\n        }\n    ) {\n        Log.d(\"add_to_fav_body\", it)\n        return@websiteIOFlow WebsiteState.Success(likeStatus)\n    }\n\n    fun createPlaylist(\n        videoCode: String,\n        title: String,\n        description: String,\n        csrfToken: String?,\n    ) = websiteIOFlow(\n        request = {\n            HanimeNetwork.myListService.createPlaylist(\n                csrfToken, videoCode, title, description\n            )\n        },\n        permittedSuccessCode = intArrayOf(500)\n    ) {\n        Log.d(\"create_playlist_body\", it)\n        return@websiteIOFlow WebsiteState.Success(Unit)\n    }\n\n    fun addToMyList(\n        listCode: String,\n        videoCode: String,\n        isChecked: Boolean,\n        position: Int,\n        csrfToken: String?,\n    ) = websiteIOFlow(\n        request = {\n            HanimeNetwork.myListService.addToMyList(\n                csrfToken, listCode, videoCode, isChecked\n            )\n        }\n    ) {\n        Log.d(\"add_to_playlist_body\", it)\n        return@websiteIOFlow WebsiteState.Success(position)\n    }\n\n    fun modifyPlaylist(\n        listCode: String,\n        title: String,\n        description: String,\n        delete: Boolean,\n        csrfToken: String?,\n    ) = websiteIOFlow(\n        request = {\n            HanimeNetwork.myListService.modifyPlaylist(\n                listCode, title, description,\n                if (delete) \"on\" else null,\n                csrfToken\n            )\n        },\n        permittedSuccessCode = intArrayOf(302)\n    ) {\n        Log.d(\"modify_playlist_body\", it)\n        return@websiteIOFlow WebsiteState.Success(\n            ModifiedPlaylistArgs(\n                title = title, desc = description, isDeleted = delete,\n            )\n        )\n    }\n\n    //</editor-fold>\n\n    //<editor-fold desc=\"Comment\">\n\n    fun getComments(type: String, code: String) = websiteIOFlow(\n        request = { HanimeNetwork.commentService.getComments(type, code) },\n        action = Parser::comments\n    )\n\n    fun getCommentReply(commentId: String) = websiteIOFlow(\n        request = { HanimeNetwork.commentService.getCommentReply(commentId) },\n        action = Parser::commentReply\n    )\n\n    fun postComment(\n        csrfToken: String?,\n        currentUserId: String,\n        targetUserId: String,\n        type: String,\n        text: String,\n    ) = websiteIOFlow(\n        request = {\n            HanimeNetwork.commentService.postComment(\n                csrfToken, currentUserId,\n                type, targetUserId, text\n            )\n        }\n    ) {\n        Log.d(\"post_comment_body\", it)\n        return@websiteIOFlow WebsiteState.Success(Unit)\n    }\n\n    fun postCommentReply(\n        csrfToken: String?,\n        replyCommentId: String,\n        text: String,\n    ) = websiteIOFlow(\n        request = {\n            HanimeNetwork.commentService.postCommentReply(\n                csrfToken, replyCommentId, text\n            )\n        }\n    ) {\n        Log.d(\"post_comment_reply_body\", it)\n        return@websiteIOFlow WebsiteState.Success(Unit)\n    }\n\n    fun likeComment(\n        csrfToken: String?,\n        commentPlace: CommentPlace,\n        foreignId: String?,\n        isPositive: Boolean, // 你選擇的是讚還是踩，1是讚，0是踩\n        likeUserId: String?,\n        commentLikesCount: Int,\n        commentLikesSum: Int,\n        likeCommentStatus: Boolean, // 你之前有沒有點過讚，1是0否\n        unlikeCommentStatus: Boolean, // 你之前有沒有點過踩，1是0否\n        commentPosition: Int, comment: VideoComments.VideoComment,\n    ) = websiteIOFlow(\n        request = {\n            HanimeNetwork.commentService.likeComment(\n                csrfToken, commentPlace.value, foreignId,\n                if (isPositive) 1 else 0,\n                likeUserId, commentLikesCount, commentLikesSum,\n                if (likeCommentStatus) 1 else 0,\n                if (unlikeCommentStatus) 1 else 0\n            )\n        }\n    ) {\n        Log.d(\"like_comment_body\", it)\n        return@websiteIOFlow WebsiteState.Success(\n            VideoCommentArgs(\n                commentPosition, isPositive, comment\n            )\n        )\n    }\n\n    //</editor-fold>\n\n    //<editor-fold desc=\"Subscription\">\n\n    fun subscribeArtist(\n        csrfToken: String?,\n        userId: String,\n        artistId: String,\n        // 这里表示目标状态\n        status: Boolean,\n    ) = websiteIOFlow(\n        request = {\n            HanimeNetwork.subscriptionService.subscribeArtist(\n                csrfToken, userId, artistId,\n                if (status) \"\" else \"1\"\n            )\n        }\n    ) {\n        Log.d(\"subscribe_artist_body\", it)\n        return@websiteIOFlow WebsiteState.Success(status)\n    }\n\n    //</editor-fold>\n\n    //<editor-fold desc=\"Base\">\n\n    fun getLatestVersion(forceCheck: Boolean = true) = flow {\n        emit(WebsiteState.Loading)\n        val versionInfo = HUpdater.checkForUpdate(forceCheck)\n        emit(WebsiteState.Success(versionInfo))\n    }.catch { e ->\n        when (e) {\n            is CancellationException -> throw e\n            else -> {\n                e.printStackTrace()\n                emit(WebsiteState.Error(e))\n            }\n        }\n    }.flowOn(Dispatchers.IO)\n\n    fun login(email: String, password: String) = flow {\n        emit(WebsiteState.Loading)\n        // 首先获取token\n        val loginPage = HanimeNetwork.hanimeService.getLoginPage()\n        val token = loginPage.body()?.string()?.let(Parser::extractTokenFromLoginPage)\n        val req = HanimeNetwork.hanimeService.login(token, email, password)\n        if (req.isSuccessful) {\n            // 再次获取登录页面，如果失败则返回 cookie\n            // 因为登录成功再次访问 login 会 404，这是判断是否登录成功的方法\n            val loginPageAgain = HanimeNetwork.hanimeService.getLoginPage()\n            if (loginPageAgain.code() == 404) {\n                // Cookie 會返回 XSRF-TOKEN 和 hanime1_session，我們只需要後者\n                // 错误的，还需要 remember_web 字段！但我没找到！\n                Log.d(\"login_headers\", req.headers().toMultimap().toString())\n                emit(WebsiteState.Success(req.headers().values(\"Set-Cookie\")))\n            } else {\n                emit(WebsiteState.Error(IllegalStateException(getString(R.string.account_or_password_wrong))))\n            }\n        } else {\n            // 雙重保險\n            emit(WebsiteState.Error(IllegalStateException(getString(R.string.account_or_password_wrong))))\n        }\n    }.catch { e ->\n        emit(WebsiteState.Error(handleException(e)))\n    }.flowOn(Dispatchers.IO)\n\n    /**\n     * 用于单网页的情况\n     *\n     * @param permittedSuccessCode 用于处理特殊情况，比如[NetworkRepo.modifyPlaylist]需要302成功\n     */\n    private fun <T> websiteIOFlow(\n        request: suspend () -> Response<ResponseBody>,\n        permittedSuccessCode: IntArray? = null,\n        action: (String) -> WebsiteState<T>,\n    ) = flow {\n        val requestResult = request.invoke()\n        val resultBody = requestResult.body()?.string()\n        val permitted = permittedSuccessCode?.contains(requestResult.code()) == true\n        if ((permitted || requestResult.isSuccessful)) {\n            emit(action.invoke(resultBody ?: EMPTY_STRING))\n        } else {\n            requestResult.throwRequestException()\n        }\n    }.catch { e ->\n        emit(WebsiteState.Error(handleException(e)))\n    }.flowOn(Dispatchers.IO)\n\n    /**\n     * 用于有page分页的情况\n     */\n    private fun <T> pageIOFlow(\n        request: suspend () -> Response<ResponseBody>,\n        action: (String) -> PageLoadingState<T>,\n    ) = flow {\n        val requestResult = request.invoke()\n        val resultBody = requestResult.body()?.string()\n        if (requestResult.isSuccessful && resultBody != null) {\n            emit(action.invoke(resultBody))\n        } else {\n            requestResult.throwRequestException()\n        }\n    }.catch { e ->\n        emit(PageLoadingState.Error(handleException(e)))\n    }.flowOn(Dispatchers.IO)\n\n    /**\n     * 用于影片界面\n     */\n    private fun <T> videoIOFlow(\n        request: suspend () -> Response<ResponseBody>,\n        action: (String) -> VideoLoadingState<T>,\n    ) = flow {\n        val requestResult = request.invoke()\n        val resultBody = requestResult.body()?.string()\n        if (requestResult.isSuccessful && resultBody != null) {\n            emit(action.invoke(resultBody))\n        } else {\n            requestResult.throwRequestException()\n        }\n    }.catch { e ->\n        emit(VideoLoadingState.Error(handleException(e)))\n    }.flowOn(Dispatchers.IO)\n\n    private fun Response<ResponseBody>.throwRequestException(): Nothing {\n        val body = errorBody()?.string()\n        when (val code = code()) {\n            403 -> if (!body.isNullOrBlank()) {\n                when {\n                    \"you have been blocked\" in body ->\n                        throw IPBlockedException(getString(R.string.do_not_use_japan_ip))\n\n                    \"Just a moment\" in body ->\n                        throw CloudFlareBlockedException(getString(CloudFlareBlockedException.localizedMessages.random()))\n\n                    else ->\n                        throw HanimeNotFoundException(getString(R.string.video_might_not_exist)) // 主要出現在影片界面，當你v數不大時會報403\n                }\n            } else throw IllegalStateException(\"$code ${message()}\")\n\n            500 -> throw HanimeNotFoundException(getString(R.string.video_might_not_exist)) // 主要出現在影片界面，當你v數很大時會報500\n\n            404 -> if (!isAlreadyLogin) {\n                throw IllegalStateException(getString(R.string.not_logged_in_currently))\n            } else {\n                throw IllegalStateException(\"$code ${message()}\")\n            }\n\n            else -> throw IllegalStateException(\"$code ${message()}\")\n        }\n    }\n\n    private fun handleException(e: Throwable): Throwable {\n        return when (e) {\n            is CancellationException -> throw e\n            is ParseException -> {\n                e.printStackTrace()\n                ParseException(getString(R.string.parse_error_msg))\n            }\n\n            is SSLHandshakeException -> {\n                e.printStackTrace()\n                SSLHandshakeException(getString(R.string.network_unstable_msg))\n            }\n\n            else -> {\n                e.printStackTrace()\n                e\n            }\n        }\n    }\n\n    //</editor-fold>\n\n    private fun getString(resId: Int) = applicationContext.getString(resId)\n}"
  },
  {
    "path": "app/src/main/java/com/yenaly/han1meviewer/logic/Parser.kt",
    "content": "package com.yenaly.han1meviewer.logic\n\nimport android.annotation.SuppressLint\nimport android.util.Log\nimport com.yenaly.han1meviewer.EMPTY_STRING\nimport com.yenaly.han1meviewer.HanimeResolution\nimport com.yenaly.han1meviewer.LOCAL_DATE_FORMAT\nimport com.yenaly.han1meviewer.Preferences.isAlreadyLogin\nimport com.yenaly.han1meviewer.logic.exception.ParseException\nimport com.yenaly.han1meviewer.logic.model.HanimeInfo\nimport com.yenaly.han1meviewer.logic.model.HanimePreview\nimport com.yenaly.han1meviewer.logic.model.HanimeVideo\nimport com.yenaly.han1meviewer.logic.model.HomePage\nimport com.yenaly.han1meviewer.logic.model.MyListItems\nimport com.yenaly.han1meviewer.logic.model.Playlists\nimport com.yenaly.han1meviewer.logic.model.Subscription\nimport com.yenaly.han1meviewer.logic.model.VideoComments\nimport com.yenaly.han1meviewer.logic.state.PageLoadingState\nimport com.yenaly.han1meviewer.logic.state.VideoLoadingState\nimport com.yenaly.han1meviewer.logic.state.WebsiteState\nimport com.yenaly.han1meviewer.toVideoCode\nimport kotlinx.datetime.LocalDate\nimport org.json.JSONObject\nimport org.jsoup.Jsoup\nimport org.jsoup.nodes.Comment\nimport org.jsoup.nodes.Element\nimport org.jsoup.select.Elements\n\n/**\n * @project Han1meViewer\n * @author Yenaly Liew\n * @time 2023/07/31 031 16:43\n */\nobject Parser {\n\n    /**\n     * 所需 Regex\n     */\n    object Regex {\n        val videoSource = Regex(\"\"\"const source = '(.+)'\"\"\")\n        val viewAndUploadTime = Regex(\"\"\"觀看次數：(.+)次 *(\\d{4}-\\d{2}-\\d{2})\"\"\")\n    }\n\n    fun extractTokenFromLoginPage(body: String): String {\n        val parseBody = Jsoup.parse(body).body()\n        return parseBody.selectFirst(\"input[name=_token]\")?.attr(\"value\")\n            ?: throw ParseException(\"Can't find csrf token from login page.\")\n    }\n\n    fun homePageVer2(body: String): WebsiteState<HomePage> {\n        val parseBody = Jsoup.parse(body).body()\n        val csrfToken = parseBody.selectFirst(\"input[name=_token]\")?.attr(\"value\") // csrf token\n\n        val homePageParse = parseBody.select(\"div[id=home-rows-wrapper] > div\")\n        val userInfo = parseBody.selectFirst(\"div[id=user-modal-dp-wrapper]\")\n        val avatarUrl: String? = userInfo?.selectFirst(\"img\")?.absUrl(\"src\")\n        val username: String? = userInfo?.getElementById(\"user-modal-name\")?.text()\n\n        val bannerCSS = parseBody.selectFirst(\"div[id=home-banner-wrapper]\")\n        val bannerImg = bannerCSS?.previousElementSibling()\n        val bannerTitle = bannerImg?.selectFirst(\"img\")?.attr(\"alt\")\n            .logIfParseNull(Parser::homePageVer2.name, \"bannerTitle\")\n        val bannerPic = bannerImg?.select(\"img\")?.getOrNull(1)?.absUrl(\"src\")\n            .logIfParseNull(Parser::homePageVer2.name, \"bannerPic\")\n        val bannerDesc = bannerCSS?.selectFirst(\"h4\")?.ownText()\n        var bannerVideoCode =\n            bannerCSS?.selectFirst(\"a[class~=play-btn]\")?.absUrl(\"href\")?.toVideoCode()\n        // 目前先判断注释里的，以后可能会有变化\n        if (bannerVideoCode == null) {\n            bannerCSS?.traverse { node, _ ->\n                if (node is Comment) {\n                    node.data.toVideoCode()?.let {\n                        bannerVideoCode = it\n                        return@traverse\n                    }\n                }\n            }\n        }\n        bannerVideoCode.logIfParseNull(Parser::homePageVer2.name, \"bannerVideoCode\")\n        val banner = if (bannerTitle != null && bannerPic != null) {\n            HomePage.Banner(\n                title = bannerTitle, description = bannerDesc,\n                picUrl = bannerPic, videoCode = bannerVideoCode,\n            )\n        } else null\n\n        val latestHanimeClass = homePageParse.getOrNull(0)\n        val latestReleaseClass = homePageParse.getOrNull(1)\n        val latestUploadClass = homePageParse.getOrNull(2)\n        val chineseSubtitleClass = homePageParse.getOrNull(3)\n        val hotHanimeMonthlyClass = homePageParse.getOrNull(homePageParse.size - 2)\n        val hanimeCurrentClass = homePageParse.getOrNull(homePageParse.size - 3)\n        val hanimeTheyWatchedClass = homePageParse.getOrNull(4)\n\n        // for latest hanime\n        val latestHanimeList = mutableListOf<HanimeInfo>()\n        val latestHanimeItems = latestHanimeClass?.select(\"div[class=home-rows-videos-div]\")\n        latestHanimeItems?.forEach { latestHanimeItem ->\n            val coverUrl = latestHanimeItem.selectFirst(\"img\")?.absUrl(\"src\")\n                .throwIfParseNull(Parser::homePageVer2.name, \"coverUrl\")\n            val title = latestHanimeItem.selectFirst(\"div[class$=title]\")?.text()\n                .throwIfParseNull(Parser::homePageVer2.name, \"title\")\n            val videoCode = latestHanimeItem.parent()?.absUrl(\"href\")?.toVideoCode()\n                .throwIfParseNull(Parser::homePageVer2.name, \"videoCode\")\n            latestHanimeList.add(\n                HanimeInfo(\n                    coverUrl = coverUrl,\n                    title = title,\n                    videoCode = videoCode,\n                    itemType = HanimeInfo.SIMPLIFIED\n                )\n            )\n        }\n\n        // for latest release\n        val latestReleaseList = mutableListOf<HanimeInfo>()\n        val latestReleaseItems = latestReleaseClass?.select(\"div[class^=card-mobile-panel]\")\n        latestReleaseItems?.forEachStep2 { latestReleaseItem ->\n            hanimeNormalItemVer2(latestReleaseItem)?.let(latestReleaseList::add)\n        }\n\n        // for latest upload\n        val latestUploadList = mutableListOf<HanimeInfo>()\n        val latestUploadItems = latestUploadClass?.select(\"div[class^=card-mobile-panel]\")\n        latestUploadItems?.forEachStep2 { latestUploadItem ->\n            hanimeNormalItemVer2(latestUploadItem)?.let(latestUploadList::add)\n        }\n\n        // for chinese subtitle\n        val chineseSubtitleList = mutableListOf<HanimeInfo>()\n        val chineseSubtitleItems = chineseSubtitleClass?.select(\"div[class^=card-mobile-panel]\")\n        chineseSubtitleItems?.forEachStep2 { chineseSubtitleItem ->\n            hanimeNormalItemVer2(chineseSubtitleItem)?.let(chineseSubtitleList::add)\n        }\n\n        // for hanime they watched\n        val hanimeTheyWatchedList = mutableListOf<HanimeInfo>()\n        val hanimeTheyWatchedItems =\n            hanimeTheyWatchedClass?.select(\"div[class^=card-mobile-panel]\")\n        hanimeTheyWatchedItems?.forEachStep2 { hanimeTheyWatchedItem ->\n            hanimeNormalItemVer2(hanimeTheyWatchedItem)?.let(hanimeTheyWatchedList::add)\n        }\n\n        // for hanime current\n        val hanimeCurrentList = mutableListOf<HanimeInfo>()\n        val hanimeCurrentItems =\n            hanimeCurrentClass?.select(\"div[class^=card-mobile-panel]\")\n        hanimeCurrentItems?.forEachStep2 { hanimeCurrentItem ->\n            hanimeNormalItemVer2(hanimeCurrentItem)?.let(hanimeCurrentList::add)\n        }\n\n        // for hot hanime monthly\n        val hotHanimeMonthlyList = mutableListOf<HanimeInfo>()\n        val hotHanimeMonthlyItems =\n            hotHanimeMonthlyClass?.select(\"div[class^=card-mobile-panel]\")\n        hotHanimeMonthlyItems?.forEachStep2 { hotHanimeMonthlyItem ->\n            hanimeNormalItemVer2(hotHanimeMonthlyItem)?.let(hotHanimeMonthlyList::add)\n        }\n\n        // emit!\n        return WebsiteState.Success(\n            HomePage(\n                csrfToken,\n                avatarUrl, username, banner = banner,\n                latestHanime = latestHanimeList,\n                latestRelease = latestReleaseList,\n                latestUpload = latestUploadList,\n                chineseSubtitle = chineseSubtitleList,\n                hanimeTheyWatched = hanimeTheyWatchedList,\n                hanimeCurrent = hanimeCurrentList,\n                hotHanimeMonthly = hotHanimeMonthlyList,\n            )\n        )\n    }\n\n    fun hanimeSearch(body: String): PageLoadingState<MutableList<HanimeInfo>> {\n        val parseBody = Jsoup.parse(body).body()\n        val allContentsClass =\n            parseBody.getElementsByClass(\"content-padding-new\").firstOrNull()\n        val allSimplifiedContentsClass =\n            parseBody.getElementsByClass(\"home-rows-videos-wrapper\").firstOrNull()\n\n        // emit!\n        if (allContentsClass != null) {\n            return hanimeSearchNormalVer2(allContentsClass)\n        } else if (allSimplifiedContentsClass != null) {\n            return hanimeSearchSimplified(allSimplifiedContentsClass)\n        }\n        return PageLoadingState.Success(mutableListOf())\n    }\n\n    // 每一个正常视频单元\n    // #issue-38: 解析錯誤，原來是加廣告了！所以遇到無法處理的直接返回null。\n    private fun hanimeNormalItemVer2(hanimeSearchItem: Element): HanimeInfo? {\n        val title =\n            hanimeSearchItem.selectFirst(\"div[class=card-mobile-title]\")?.text()\n                .logIfParseNull(Parser::hanimeNormalItemVer2.name, \"title\") // title\n        val coverUrl =\n            hanimeSearchItem.select(\"img\").getOrNull(1)?.absUrl(\"src\")\n                .logIfParseNull(Parser::hanimeNormalItemVer2.name, \"coverUrl\") // coverUrl\n        val videoCode =\n            hanimeSearchItem.previousElementSibling()?.absUrl(\"href\")?.toVideoCode()\n                .logIfParseNull(Parser::hanimeNormalItemVer2.name, \"videoCode\") // videoCode\n        if (title == null || coverUrl == null || videoCode == null) return null\n        val durationAndViews = hanimeSearchItem.select(\"div.card-mobile-duration\")\n        val mDuration = durationAndViews.getOrNull(0)?.text() // 改了\n        val views = durationAndViews.getOrNull(2)?.text() // 改了\n        return HanimeInfo(\n            title = title,\n            coverUrl = coverUrl,\n            videoCode = videoCode,\n            duration = mDuration.logIfParseNull(Parser::hanimeNormalItemVer2.name, \"duration\"),\n            uploader = null,\n            views = views.logIfParseNull(Parser::hanimeNormalItemVer2.name, \"views\"),\n            uploadTime = null,\n            genre = null,\n            itemType = HanimeInfo.NORMAL\n        )\n    }\n\n    // 每一个简化版视频单元\n    private fun hanimeSimplifiedItem(hanimeSearchItem: Element): HanimeInfo? {\n        val videoCode = hanimeSearchItem.attr(\"href\").toVideoCode()\n            .logIfParseNull(Parser::hanimeSimplifiedItem.name, \"videoCode\")\n        val coverUrl = hanimeSearchItem.selectFirst(\"img\")?.attr(\"src\")\n            .logIfParseNull(Parser::hanimeSimplifiedItem.name, \"coverUrl\")\n        val title = hanimeSearchItem.selectFirst(\"div[class=home-rows-videos-title]\")?.text()\n            .logIfParseNull(Parser::hanimeSimplifiedItem.name, \"title\")\n        if (videoCode == null || coverUrl == null || title == null) return null\n        return HanimeInfo(\n            title = title,\n            coverUrl = coverUrl,\n            videoCode = videoCode,\n            itemType = HanimeInfo.SIMPLIFIED\n        )\n    }\n\n    // 出来后是正常视频单元的页面用这个\n    private fun hanimeSearchNormalVer2(\n        allContentsClass: Element,\n    ): PageLoadingState<MutableList<HanimeInfo>> {\n        val hanimeSearchList = mutableListOf<HanimeInfo>()\n        val hanimeSearchItems =\n            allContentsClass.select(\"div[class^=card-mobile-panel]\")\n        if (hanimeSearchItems.isEmpty()) {\n            return PageLoadingState.NoMoreData\n        } else {\n            hanimeSearchItems.forEachStep2 { hanimeSearchItem ->\n                hanimeNormalItemVer2(hanimeSearchItem)?.let(hanimeSearchList::add)\n            }\n        }\n        Log.d(\"search_result\", \"$hanimeSearchList\")\n        return PageLoadingState.Success(hanimeSearchList)\n    }\n\n    // 出来后是简化版视频单元的页面用这个\n    private fun hanimeSearchSimplified(\n        allSimplifiedContentsClass: Element,\n    ): PageLoadingState<MutableList<HanimeInfo>> {\n        val hanimeSearchList = mutableListOf<HanimeInfo>()\n        val hanimeSearchItems = allSimplifiedContentsClass.children()\n        if (hanimeSearchItems.isEmpty()) {\n            return PageLoadingState.NoMoreData\n        } else hanimeSearchItems.forEach { hanimeSearchItem ->\n            hanimeSimplifiedItem(hanimeSearchItem)?.let(hanimeSearchList::add)\n        }\n        return PageLoadingState.Success(hanimeSearchList)\n    }\n\n    fun hanimeVideoVer2(body: String): VideoLoadingState<HanimeVideo> {\n        val parseBody = Jsoup.parse(body).body()\n        val csrfToken = parseBody.selectFirst(\"input[name=_token]\")?.attr(\"value\") // csrf token\n\n        val currentUserId =\n            parseBody.selectFirst(\"input[name=like-user-id]\")?.attr(\"value\") // current user id\n\n        val title = parseBody.getElementById(\"shareBtn-title\")?.text()\n            .throwIfParseNull(Parser::hanimeVideoVer2.name, \"title\")\n\n        val likeStatus = parseBody.selectFirst(\"#video-like-btn .material-icons\")\n        val likesCount = parseBody.selectFirst(\"input[name=likes-count]\")\n            ?.attr(\"value\")?.toIntOrNull()\n\n        val videoDetailWrapper = parseBody.selectFirst(\"div[class=video-details-wrapper]\")\n        val videoCaptionText = videoDetailWrapper?.selectFirst(\"div[class^=video-caption-text]\")\n        val chineseTitle = videoCaptionText?.previousElementSibling()?.ownText()\n        val introduction = videoCaptionText?.ownText()\n        val uploadTimeWithViews = videoDetailWrapper?.selectFirst(\"div > div > div\")?.text()\n        val uploadTimeWithViewsGroups = uploadTimeWithViews?.let {\n            Regex.viewAndUploadTime.find(it)?.groups\n        }\n        val uploadTime = uploadTimeWithViewsGroups?.get(2)?.value?.let { time ->\n            LocalDate.parse(time, LOCAL_DATE_FORMAT)\n        }\n\n        val views = uploadTimeWithViewsGroups?.get(1)?.value\n\n        val tags = parseBody.getElementsByClass(\"single-video-tag\")\n        val tagList = mutableListOf<String>()\n        tags.forEach { tag ->\n            val child = tag.childOrNull(0)\n            if (child != null && child.hasAttr(\"href\")) {\n                tagList.add(child.ownText())\n            }\n        }\n\n        val myListCheckboxWrapper = parseBody.select(\"div[class~=playlist-checkbox-wrapper]\")\n        val myListInfo = mutableListOf<HanimeVideo.MyList.MyListInfo>()\n        myListCheckboxWrapper.forEach {\n            val listTitle = it.selectFirst(\"span\")?.ownText()\n                .logIfParseNull(Parser::hanimeVideoVer2.name, \"myListTitle\", loginNeeded = true)\n            val listInput = it.selectFirst(\"input\")\n            val listCode = listInput?.attr(\"id\")\n                .logIfParseNull(Parser::hanimeVideoVer2.name, \"myListCode\", loginNeeded = true)\n            val isSelected = listInput?.hasAttr(\"checked\") == true\n            if (listTitle != null && listCode != null) {\n                myListInfo += HanimeVideo.MyList.MyListInfo(\n                    code = listCode, title = listTitle, isSelected = isSelected\n                )\n            }\n        }\n        val isWatchLater = parseBody.getElementById(\"playlist-save-checkbox\")\n            ?.selectFirst(\"input\")?.hasAttr(\"checked\") == true\n        val myList = HanimeVideo.MyList(isWatchLater = isWatchLater, myListInfo = myListInfo)\n\n        val playlistWrapper = parseBody.selectFirst(\"div[id=video-playlist-wrapper]\")\n        val playlist = playlistWrapper?.let {\n            val playlistVideoList = mutableListOf<HanimeInfo>()\n            val playlistName = it.selectFirst(\"div > div > h4\")?.text()\n            val playlistScroll = it.getElementById(\"playlist-scroll\")\n            playlistScroll?.children()?.forEach { parent ->\n                if (parent.selectFirst(\".load-more-related-link\") != null) return@forEach\n                val videoCode = parent.selectFirst(\"div > a\")?.absUrl(\"href\")?.toVideoCode()\n                    .throwIfParseNull(Parser::hanimeVideoVer2.name, \"videoCode\")\n                val cardMobilePanel = parent.selectFirst(\"div[class^=card-mobile-panel]\")\n                val eachTitleCover = cardMobilePanel?.select(\"div > div > div > img\")?.getOrNull(1)\n                val eachIsPlaying = cardMobilePanel?.select(\"div > div > div > div\")\n                    ?.firstOrNull()\n                    ?.text()\n                    ?.contains(\"播放\") == true\n                val cardMobileDuration = cardMobilePanel?.select(\"div.card-mobile-duration\")\n                val eachDuration = cardMobileDuration?.firstOrNull()?.text()\n                val eachViews = cardMobileDuration?.getOrNull(2)?.text()\n                    ?.substringBefore(\"次\")\n                val playlistEachCoverUrl = eachTitleCover?.absUrl(\"src\")\n                    .throwIfParseNull(Parser::hanimeVideoVer2.name, \"playlistEachCoverUrl\")\n                val playlistEachTitle = eachTitleCover?.attr(\"alt\")\n                    .throwIfParseNull(Parser::hanimeVideoVer2.name, \"playlistEachTitle\")\n                playlistVideoList.add(\n                    HanimeInfo(\n                        title = playlistEachTitle, coverUrl = playlistEachCoverUrl,\n                        videoCode = videoCode,\n                        duration = eachDuration.logIfParseNull(\n                            Parser::hanimeVideoVer2.name,\n                            \"$playlistEachTitle duration\"\n                        ),\n                        views = eachViews.logIfParseNull(\n                            Parser::hanimeVideoVer2.name,\n                            \"$playlistEachTitle views\"\n                        ),\n                        isPlaying = eachIsPlaying,\n                        itemType = HanimeInfo.NORMAL\n                    )\n                )\n            }\n            HanimeVideo.Playlist(playlistName = playlistName, video = playlistVideoList)\n        }\n\n        val relatedAnimeList = mutableListOf<HanimeInfo>()\n        val relatedTabContent = parseBody.getElementById(\"related-tabcontent\")\n\n        relatedTabContent?.also {\n            val children = it.childOrNull(0)?.children()\n            val isSimplified =\n                children?.getOrNull(0)?.select(\"a\")?.getOrNull(0)\n                    ?.getElementsByClass(\"home-rows-videos-div\")\n                    ?.firstOrNull() != null\n            if (isSimplified) {\n                for (each in children) {\n                    val eachContent = each.selectFirst(\"a\")\n                    val homeRowsVideosDiv =\n                        eachContent?.getElementsByClass(\"home-rows-videos-div\")?.firstOrNull()\n\n                    if (homeRowsVideosDiv != null) {\n                        val eachVideoCode = eachContent.absUrl(\"href\").toVideoCode() ?: continue\n                        val eachCoverUrl = homeRowsVideosDiv.selectFirst(\"img\")?.absUrl(\"src\")\n                            .throwIfParseNull(Parser::hanimeVideoVer2.name, \"eachCoverUrl\")\n                        val eachTitle =\n                            homeRowsVideosDiv.selectFirst(\"div[class$=title]\")?.text()\n                                .throwIfParseNull(Parser::hanimeVideoVer2.name, \"eachTitle\")\n                        relatedAnimeList.add(\n                            HanimeInfo(\n                                title = eachTitle, coverUrl = eachCoverUrl,\n                                videoCode = eachVideoCode,\n                                itemType = HanimeInfo.SIMPLIFIED\n                            )\n                        )\n                    }\n                }\n            } else {\n                children?.forEachStep2 { each ->\n                    val item = each.select(\"div[class^=card-mobile-panel]\")[0]\n                    hanimeNormalItemVer2(item)?.let(relatedAnimeList::add)\n                }\n            }\n        }\n        Log.d(\"related_anime_list\", relatedAnimeList.toString())\n\n        val hanimeResolution = HanimeResolution()\n        val videoClass = parseBody.selectFirst(\"video[id=player]\")\n        val videoCoverUrl = videoClass?.absUrl(\"poster\").orEmpty()\n        val videos = videoClass?.children()\n        if (!videos.isNullOrEmpty()) {\n            videos.forEach { source ->\n                val resolution = source.attr(\"size\") + \"P\"\n                val sourceUrl = source.absUrl(\"src\")\n                val videoType = source.attr(\"type\")\n                hanimeResolution.parseResolution(resolution, sourceUrl, videoType)\n            }\n        } else {\n            val playerDivWrapper = parseBody.selectFirst(\"div[id=player-div-wrapper]\")\n            playerDivWrapper?.select(\"script\")?.let { scripts ->\n                for (script in scripts) {\n                    val data = script.data()\n                    if (data.isBlank()) continue\n                    val result =\n                        Regex.videoSource.find(data)?.groups?.get(1)?.value ?: continue\n                    hanimeResolution.parseResolution(null, result)\n                    break\n                }\n            }\n        }\n\n        val artistAvatarUrl = parseBody.selectFirst(\"#video-user-avatar + img\")?.absUrl(\"src\")\n        val artistNameCSS = parseBody.getElementById(\"video-artist-name\")\n        val artistGenre = artistNameCSS?.nextElementSibling()?.text()?.trim()\n        val artistName = artistNameCSS?.text()?.trim()\n        val postCSS = parseBody.getElementById(\"video-subscribe-form\")\n        val post = postCSS?.let {\n            val userId = it.selectFirst(\"input[name=subscribe-user-id]\")?.attr(\"value\")\n            val artistId = it.selectFirst(\"input[name=subscribe-artist-id]\")?.attr(\"value\")\n            val isSubscribed = it.selectFirst(\"input[name=subscribe-status]\")?.attr(\"value\")\n            if (userId != null && artistId != null && isSubscribed != null) {\n                HanimeVideo.Artist.POST(\n                    userId = userId,\n                    artistId = artistId,\n                    isSubscribed = isSubscribed == \"1\"\n                )\n            } else null\n        }\n        val artist = if (artistAvatarUrl != null && artistName != null && artistGenre != null) {\n            HanimeVideo.Artist(\n                name = artistName,\n                avatarUrl = artistAvatarUrl,\n                genre = artistGenre,\n                post = post,\n            )\n        } else null\n\n        return VideoLoadingState.Success(\n            HanimeVideo(\n                title = title, coverUrl = videoCoverUrl,\n                chineseTitle = chineseTitle.logIfParseNull(\n                    Parser::hanimeVideoVer2.name,\n                    \"chineseTitle\"\n                ),\n                uploadTime = uploadTime.logIfParseNull(Parser::hanimeVideoVer2.name, \"uploadTime\"),\n                views = views.logIfParseNull(Parser::hanimeVideoVer2.name, \"views\"),\n                introduction = introduction.logIfParseNull(\n                    Parser::hanimeVideoVer2.name,\n                    \"introduction\"\n                ),\n                videoUrls = hanimeResolution.toResolutionLinkMap(),\n                tags = tagList,\n                myList = myList,\n                playlist = playlist,\n                relatedHanimes = relatedAnimeList,\n                artist = artist.logIfParseNull(Parser::hanimeVideoVer2.name, \"artist\"),\n                favTimes = likesCount,\n                isFav = likeStatus != null,\n                csrfToken = csrfToken,\n                currentUserId = currentUserId\n            )\n        )\n    }\n\n    fun hanimePreview(body: String): WebsiteState<HanimePreview> {\n        val parseBody = Jsoup.parse(body).body()\n\n        // latest hanime\n        val latestHanimeList = mutableListOf<HanimeInfo>()\n        val latestHanimeClass = parseBody.selectFirst(\"div[class$=owl-theme]\")\n        latestHanimeClass?.let {\n            val latestHanimeItems = latestHanimeClass.select(\"div[class=home-rows-videos-div]\")\n            latestHanimeItems.forEach { latestHanimeItem ->\n                val coverUrl = latestHanimeItem.selectFirst(\"img\")?.absUrl(\"src\")\n                    .throwIfParseNull(Parser::hanimePreview.name, \"coverUrl\")\n                val title = latestHanimeItem.selectFirst(\"div[class$=title]\")?.text()\n                    .throwIfParseNull(Parser::hanimePreview.name, \"title\")\n                latestHanimeList.add(\n                    HanimeInfo(\n                        coverUrl = coverUrl,\n                        title = title,\n                        videoCode = EMPTY_STRING /* empty string here! */,\n                        itemType = HanimeInfo.SIMPLIFIED\n                    )\n                )\n            }\n        }\n\n        val contentPaddingClass = parseBody.select(\"div[class=content-padding] > div\")\n        val previewInfo = mutableListOf<HanimePreview.PreviewInfo>()\n        for (i in 0 until contentPaddingClass.size / 2) {\n\n            val firstPart = contentPaddingClass.getOrNull(i * 2)\n            val secondPart = contentPaddingClass.getOrNull(i * 2 + 1)\n\n            val videoCode = firstPart?.id()\n            val title = firstPart?.selectFirst(\"h4\")?.text()\n            val coverUrl =\n                firstPart?.selectFirst(\"div[class=preview-info-cover] > img\")?.absUrl(\"src\")\n            val previewInfoContentClass =\n                firstPart?.getElementsByClass(\"preview-info-content-padding\")?.firstOrNull()\n            val videoTitle = previewInfoContentClass?.selectFirst(\"h4\")?.text()\n            val brand = previewInfoContentClass?.selectFirst(\"h5\")?.selectFirst(\"a\")?.text()\n            val releaseDate = previewInfoContentClass?.select(\"h5\")?.getOrNull(1)?.ownText()\n\n            val introduction = secondPart?.selectFirst(\"h5\")?.text()\n            val tagClass = secondPart?.select(\"div[class=single-video-tag] > a\")\n            val tags = mutableListOf<String>()\n            tagClass?.forEach { tag: Element? ->\n                tag?.let { tags.add(tag.text()) }\n            }\n            val relatedPicClass = secondPart?.select(\"img[class=preview-image-modal-trigger]\")\n            val relatedPics = mutableListOf<String>()\n            relatedPicClass?.forEach { relatedPic: Element? ->\n                relatedPic?.let { relatedPics.add(relatedPic.absUrl(\"src\")) }\n            }\n\n            previewInfo.add(\n                HanimePreview.PreviewInfo(\n                    title = title,\n                    videoTitle = videoTitle,\n                    coverUrl = coverUrl,\n                    introduction = introduction.logIfParseNull(\n                        Parser::hanimePreview.name,\n                        \"$title introduction\"\n                    ),\n                    brand = brand.logIfParseNull(Parser::hanimePreview.name, \"$title brand\"),\n                    releaseDate = releaseDate.logIfParseNull(\n                        Parser::hanimePreview.name,\n                        \"$title releaseDate\"\n                    ),\n                    videoCode = videoCode.logIfParseNull(\n                        Parser::hanimePreview.name,\n                        \"$title videoCode\"\n                    ),\n                    tags = tags,\n                    relatedPicsUrl = relatedPics\n                )\n            )\n        }\n\n        val header = parseBody.selectFirst(\"div[id=player-div-wrapper]\")\n        val headerPicUrl = header?.selectFirst(\"img\")?.absUrl(\"src\")\n        val hasPrevious = parseBody.getElementsByClass(\"hidden-md hidden-lg\").firstOrNull()\n            ?.select(\"div[style*=left]\")?.firstOrNull() != null\n        val hasNext = parseBody.getElementsByClass(\"hidden-md hidden-lg\").firstOrNull()\n            ?.select(\"div[style*=right]\")?.firstOrNull() != null\n\n        return WebsiteState.Success(\n            HanimePreview(\n                headerPicUrl = headerPicUrl.logIfParseNull(\n                    Parser::hanimePreview.name,\n                    \"headerPicUrl\"\n                ),\n                hasPrevious = hasPrevious,\n                hasNext = hasNext,\n                latestHanime = latestHanimeList,\n                previewInfo = previewInfo\n            )\n        )\n    }\n\n    fun myListItems(body: String): PageLoadingState<MyListItems<HanimeInfo>> {\n        val parseBody = Jsoup.parse(body).body()\n        val csrfToken = parseBody.selectFirst(\"input[name=_token]\")?.attr(\"value\")\n        val desc = parseBody.getElementById(\"playlist-show-description\")?.ownText()\n\n        val myListHanimeList = mutableListOf<HanimeInfo>()\n        val allHanimeClass = parseBody.getElementsByClass(\"home-rows-videos-wrapper\").firstOrNull()\n        allHanimeClass?.let {\n            if (allHanimeClass.childrenSize() == 0) {\n                return PageLoadingState.NoMoreData\n            }\n            allHanimeClass.children().forEach { videoElement ->\n                val title =\n                    videoElement.getElementsByClass(\"home-rows-videos-title\")\n                        .firstOrNull()?.text()\n                        .throwIfParseNull(Parser::myListItems.name, \"title\")\n                val coverUrl =\n                    videoElement.select(\"img\").let {\n                        it.getOrNull(1) ?: it.firstOrNull()\n                    }?.absUrl(\"src\")\n                        .throwIfParseNull(Parser::myListItems.name, \"coverUrl\")\n                val videoCode =\n                    videoElement.getElementsByClass(\"playlist-show-links\")\n                        .firstOrNull()?.absUrl(\"href\")?.toVideoCode()\n                        .throwIfParseNull(Parser::myListItems.name, \"videoCode\")\n                myListHanimeList.add(\n                    HanimeInfo(\n                        title = title, coverUrl = coverUrl,\n                        videoCode = videoCode, itemType = HanimeInfo.SIMPLIFIED\n                    )\n                )\n            }\n        }.logIfParseNull(Parser::myListItems.name, \"allHanimeClass_CSS\")\n\n        return PageLoadingState.Success(\n            MyListItems(\n                myListHanimeList,\n                desc = desc,\n                csrfToken = csrfToken\n            )\n        )\n    }\n\n    fun subscriptionItems(body: String): PageLoadingState<MyListItems<Subscription>> {\n        val parseBody = Jsoup.parse(body).body()\n        val csrfToken = parseBody.selectFirst(\"input[name=_token]\")?.attr(\"value\")\n\n        val subscriptionList = mutableListOf<Subscription>()\n        val allArtistsClass = parseBody.getElementsByClass(\"home-rows-videos-wrapper\").firstOrNull()\n        allArtistsClass?.let { artistClass ->\n            if (allArtistsClass.childrenSize() == 0) {\n                return PageLoadingState.NoMoreData\n            }\n            allArtistsClass.children().forEach { artistElement ->\n                val title = artistElement.selectFirst(\"div[class$=search-artist-title]\")?.text()\n                    .logIfParseNull(Parser::subscriptionItems.name, \"title\", loginNeeded = true)\n                val avatarUrl = artistElement.select(\"img\").let {\n                    it.getOrNull(1) ?: it.firstOrNull()\n                }?.absUrl(\"src\")\n                    .logIfParseNull(Parser::subscriptionItems.name, \"avatarUrl\", loginNeeded = true)\n                val artistId =\n                    artistElement.selectFirst(\"input[name=playlist-show-video-id]\")\n                        ?.attr(\"value\").logIfParseNull(\n                            Parser::subscriptionItems.name, \"artistId\", loginNeeded = true\n                        )\n                if (title != null) {\n                    subscriptionList += Subscription(\n                        name = title, avatarUrl = avatarUrl,\n                        artistId = artistId\n                    )\n                }\n            }\n        }.logIfParseNull(Parser::subscriptionItems.name, \"allArtistsClass_CSS\")\n\n        return PageLoadingState.Success(\n            MyListItems(\n                subscriptionList,\n                desc = null,\n                csrfToken = csrfToken\n            )\n        )\n    }\n\n    fun playlists(body: String): WebsiteState<Playlists> {\n        val parseBody = Jsoup.parse(body).body()\n        val csrfToken = parseBody.selectFirst(\"input[name=_token]\")?.attr(\"value\")\n        val lists = parseBody.select(\"div[class~=single-user-playlist]\")\n        val playlists = mutableListOf<Playlists.Playlist>()\n        lists.forEach {\n            val listCode = it.childOrNull(0)?.absUrl(\"href\")?.substringAfter('=')\n                .throwIfParseNull(Parser::playlists.name, \"listCode\")\n            val listTitle = it.selectFirst(\"div[class=card-mobile-title]\")?.ownText()\n                .throwIfParseNull(Parser::playlists.name, \"listTitle\")\n            val listTotal = it.selectFirst(\"div[style]\")?.text()?.toIntOrNull()\n                .throwIfParseNull(Parser::playlists.name, \"listName\")\n            playlists += Playlists.Playlist(\n                listCode = listCode, title = listTitle, total = listTotal\n            )\n        }\n        return WebsiteState.Success(Playlists(playlists = playlists, csrfToken = csrfToken))\n    }\n\n    @SuppressLint(\"BuildListAdds\")\n    fun comments(body: String): WebsiteState<VideoComments> {\n        val jsonObject = JSONObject(body)\n        val commentBody = jsonObject.get(\"comments\").toString()\n        val parseBody = Jsoup.parse(commentBody).body()\n        val csrfToken = parseBody.selectFirst(\"input[name=_token]\")?.attr(\"value\")\n        val currentUserId = parseBody.selectFirst(\"input[name=comment-user-id]\")?.attr(\"value\")\n        val commentList = mutableListOf<VideoComments.VideoComment>()\n        val allCommentsClass = parseBody.getElementById(\"comment-start\")\n\n        buildList {\n            allCommentsClass?.children()?.chunked(4)?.forEach { elements ->\n                this += Element(\"div\").apply { appendChildren(elements) }\n            }\n        }.forEach { child: Element ->\n            val avatarUrl = child.selectFirst(\"img\")?.absUrl(\"src\")\n                .throwIfParseNull(Parser::comments.name, \"avatarUrl\")\n            val textClass = child.getElementsByClass(\"comment-index-text\")\n            val nameAndDateClass = textClass.firstOrNull()\n            val username = nameAndDateClass?.selectFirst(\"a\")?.ownText()?.trim()\n                .throwIfParseNull(Parser::comments.name, \"username\")\n            val date = nameAndDateClass?.selectFirst(\"span\")?.ownText()?.trim()\n                .throwIfParseNull(Parser::comments.name, \"date\")\n            val content = textClass.getOrNull(1)?.text()\n                .throwIfParseNull(Parser::comments.name, \"content\")\n            val hasMoreReplies = child.selectFirst(\"div[class^=load-replies-btn]\") != null\n            val thumbUp = child.getElementById(\"comment-like-form-wrapper\")\n                ?.select(\"span[style]\")?.getOrNull(1)\n                ?.text()?.toIntOrNull()\n            val id = child.selectFirst(\"div[id^=reply-section-wrapper]\")\n                ?.id()?.substringAfterLast(\"-\")\n\n            val foreignId = child.getElementById(\"foreign_id\")?.attr(\"value\")\n            val isPositive = child.getElementById(\"is_positive\")?.attr(\"value\")\n            val likeUserId = child.selectFirst(\"input[name=comment-like-user-id]\")?.attr(\"value\")\n            val commentLikesCount =\n                child.selectFirst(\"input[name=comment-likes-count]\")?.attr(\"value\")\n            val commentLikesSum = child.selectFirst(\"input[name=comment-likes-sum]\")?.attr(\"value\")\n            val likeCommentStatus =\n                child.selectFirst(\"input[name=like-comment-status]\")?.attr(\"value\")\n            val unlikeCommentStatus =\n                child.selectFirst(\"input[name=unlike-comment-status]\")?.attr(\"value\")\n\n            val post = VideoComments.VideoComment.POST(\n                foreignId.logIfParseNull(Parser::comments.name, \"foreignId\", loginNeeded = true),\n                isPositive == \"1\",\n                likeUserId.logIfParseNull(Parser::comments.name, \"likeUserId\", loginNeeded = true),\n                commentLikesCount?.toIntOrNull().logIfParseNull(\n                    Parser::comments.name,\n                    \"commentLikesCount\", loginNeeded = true\n                ),\n                commentLikesSum?.toIntOrNull().logIfParseNull(\n                    Parser::comments.name,\n                    \"commentLikesSum\", loginNeeded = true\n                ),\n                likeCommentStatus == \"1\",\n                unlikeCommentStatus == \"1\",\n            )\n            commentList.add(\n                VideoComments.VideoComment(\n                    avatar = avatarUrl, username = username, date = date,\n                    content = content, hasMoreReplies = hasMoreReplies,\n                    thumbUp = thumbUp.logIfParseNull(Parser::comments.name, \"thumbUp\"),\n                    id = id.logIfParseNull(Parser::comments.name, \"id\"),\n                    isChildComment = false, post = post\n                )\n            )\n        }\n        Log.d(\"commentList\", commentList.toString())\n        return WebsiteState.Success(\n            VideoComments(\n                commentList,\n                currentUserId,\n                csrfToken\n            )\n        )\n    }\n\n    fun commentReply(body: String): WebsiteState<VideoComments> {\n        val jsonObject = JSONObject(body)\n        val replyBody = jsonObject.get(\"replies\").toString()\n        val replyList = mutableListOf<VideoComments.VideoComment>()\n        val parseBody = Jsoup.parse(replyBody).body()\n        val replyStart = parseBody.selectFirst(\"div[id^=reply-start]\")\n        replyStart?.let {\n            val allRepliesClass = it.children()\n            for (i in allRepliesClass.indices step 2) {\n                val basicClass = allRepliesClass.getOrNull(i)\n                val postClass = allRepliesClass.getOrNull(i + 1)\n\n                val avatarUrl = basicClass?.selectFirst(\"img\")?.absUrl(\"src\")\n                    .throwIfParseNull(Parser::commentReply.name, \"avatarUrl\")\n                val textClass = basicClass?.getElementsByClass(\"comment-index-text\")\n                val nameAndDateClass = textClass?.firstOrNull()\n                val username = nameAndDateClass?.selectFirst(\"a\")?.ownText()?.trim()\n                    .throwIfParseNull(Parser::commentReply.name, \"name\")\n                val date = nameAndDateClass?.selectFirst(\"span\")?.ownText()?.trim()\n                    .throwIfParseNull(Parser::commentReply.name, \"date\")\n                val content = textClass?.getOrNull(1)?.text()\n                    .throwIfParseNull(Parser::commentReply.name, \"content\")\n                val thumbUp = postClass\n                    ?.select(\"span[style]\")?.getOrNull(1)\n                    ?.text()?.toIntOrNull()\n\n                val foreignId =\n                    postClass?.getElementById(\"foreign_id\")?.attr(\"value\")\n                val isPositive =\n                    postClass?.getElementById(\"is_positive\")?.attr(\"value\")\n                val likeUserId =\n                    postClass?.selectFirst(\"input[name=comment-like-user-id]\")?.attr(\"value\")\n                val commentLikesCount =\n                    postClass?.selectFirst(\"input[name=comment-likes-count]\")?.attr(\"value\")\n                val commentLikesSum =\n                    postClass?.selectFirst(\"input[name=comment-likes-sum]\")?.attr(\"value\")\n                val likeCommentStatus =\n                    postClass?.selectFirst(\"input[name=like-comment-status]\")?.attr(\"value\")\n                val unlikeCommentStatus =\n                    postClass?.selectFirst(\"input[name=unlike-comment-status]\")?.attr(\"value\")\n                val post = VideoComments.VideoComment.POST(\n                    foreignId.logIfParseNull(\n                        Parser::commentReply.name,\n                        \"foreignId\",\n                        loginNeeded = true\n                    ),\n                    isPositive == \"1\",\n                    likeUserId.logIfParseNull(\n                        Parser::commentReply.name,\n                        \"likeUserId\",\n                        loginNeeded = true\n                    ),\n                    commentLikesCount?.toIntOrNull().logIfParseNull(\n                        Parser::commentReply.name,\n                        \"commentLikesCount\", loginNeeded = true\n                    ),\n                    commentLikesSum?.toIntOrNull().logIfParseNull(\n                        Parser::commentReply.name,\n                        \"commentLikesSum\", loginNeeded = true\n                    ),\n                    likeCommentStatus == \"1\",\n                    unlikeCommentStatus == \"1\",\n                )\n                replyList.add(\n                    VideoComments.VideoComment(\n                        avatar = avatarUrl, username = username, date = date,\n                        content = content,\n                        thumbUp = thumbUp.logIfParseNull(Parser::commentReply.name, \"thumbUp\"),\n                        id = null,\n                        isChildComment = true, post = post\n                    )\n                )\n            }\n        }\n\n        return WebsiteState.Success(VideoComments(replyList))\n    }\n\n    /**\n     * 這個網站的網頁結構真的很奇怪，所以我寫了一個 forEachStep2 來處理\n     */\n    private inline fun Elements.forEachStep2(action: (Element) -> Unit) {\n        for (i in 0 until size step 2) {\n            action(get(i))\n        }\n    }\n\n    /**\n     * 得到 Element 的 child，如果 index 超出範圍，就返回 null\n     */\n    private fun Element.childOrNull(index: Int): Element? {\n        return try {\n            child(index)\n        } catch (_: IndexOutOfBoundsException) {\n            null\n        }\n    }\n\n    /**\n     * 基本都是必需的參數，所以如果是 null，就直接丟出 [ParseException]\n     *\n     * @param funcName 這個參數是在哪個函數中被使用的\n     * @param varName 這個參數的名稱\n     * @return 如果 [this] 不是 null，就回傳 [this]\n     * @throws ParseException 如果 [this] 是 null，就丟出 [ParseException]\n     */\n    private fun <T> T?.throwIfParseNull(funcName: String, varName: String): T = this\n        ?: throw ParseException(funcName, varName)\n\n    /**\n     * 如果 [this] 是 null，就在 logcat 中顯示訊息\n     *\n     * @param funcName 這個參數是在哪個函數中被使用的\n     * @param varName 這個參數的名稱\n     * @return 回傳 [this]\n     */\n    private fun <T> T?.logIfParseNull(\n        funcName: String, varName: String, loginNeeded: Boolean = false,\n    ): T? = also {\n        if (it == null) {\n            if (loginNeeded) {\n                if (isAlreadyLogin) {\n                    Log.d(\"Parse::$funcName\", \"[$varName] is null. 而且處於登入狀態，這有點不正常\")\n                }\n            } else {\n                Log.d(\"Parse::$funcName\", \"[$varName] is null. 這有點不正常\")\n            }\n        }\n    }\n}"
  },
  {
    "path": "app/src/main/java/com/yenaly/han1meviewer/logic/dao/DownloadDatabase.kt",
    "content": "package com.yenaly.han1meviewer.logic.dao\n\nimport androidx.room.Database\nimport androidx.room.Room\nimport androidx.room.RoomDatabase\nimport androidx.room.migration.Migration\nimport androidx.sqlite.db.SupportSQLiteDatabase\nimport com.yenaly.han1meviewer.logic.dao.download.DownloadCategoryDao\nimport com.yenaly.han1meviewer.logic.dao.download.HUpdateDao\nimport com.yenaly.han1meviewer.logic.dao.download.HanimeDownloadDao\nimport com.yenaly.han1meviewer.logic.entity.download.DownloadCategoryEntity\nimport com.yenaly.han1meviewer.logic.entity.download.HUpdateEntity\nimport com.yenaly.han1meviewer.logic.entity.download.HanimeCategoryCrossRef\nimport com.yenaly.han1meviewer.logic.entity.download.HanimeDownloadEntity\nimport com.yenaly.han1meviewer.logic.state.DownloadState\nimport com.yenaly.yenaly_libs.utils.applicationContext\n\n/**\n * @project Han1meViewer\n * @author Yenaly Liew\n * @time 2022/08/07 007 18:26\n */\n@Database(\n    entities = [HanimeDownloadEntity::class, DownloadCategoryEntity::class, HanimeCategoryCrossRef::class, HUpdateEntity::class],\n    version = 4, exportSchema = false\n)\nabstract class DownloadDatabase : RoomDatabase() {\n\n    abstract val hanimeDownloadDao: HanimeDownloadDao\n    abstract val downloadCategoryDao: DownloadCategoryDao\n    abstract val hUpdateDao: HUpdateDao\n\n    companion object {\n        val instance by lazy {\n            Room.databaseBuilder(\n                applicationContext,\n                DownloadDatabase::class.java,\n                \"download.db\"\n            ).addMigrations(Migration1To2, Migration2To3, Migration3To4).build()\n        }\n    }\n\n    object Migration1To2 : Migration(1, 2) {\n        override fun migrate(db: SupportSQLiteDatabase) {\n            db.execSQL(\n                \"\"\"CREATE TABLE IF NOT EXISTS `HanimeDownloadEntity`(\n                    `coverUrl` TEXT NOT NULL, `title` TEXT NOT NULL,\n                    `addDate` INTEGER NOT NULL, `videoCode` TEXT NOT NULL,\n                    `videoUri` TEXT NOT NULL, `quality` TEXT NOT NULL,\n                    `videoUrl` TEXT NOT NULL, `length` INTEGER NOT NULL,\n                    `downloadedLength` INTEGER NOT NULL, `isDownloading` INTEGER NOT NULL,\n                    `id` INTEGER PRIMARY KEY AUTOINCREMENT NOT NULL)\"\"\".trimIndent()\n            )\n            db.execSQL(\n                \"\"\"INSERT INTO `HanimeDownloadEntity`(\n                        `coverUrl`, `title`, `addDate`,\n                        `videoCode`, `videoUri`, `quality`,\n                        `videoUrl`, `length`, `downloadedLength`, `isDownloading`, `id`)\n                     SELECT `coverUrl`, `title`, `addDate`, `videoCode`, `videoUri`, `quality`,\n                        '' AS `videoUrl`, 1 AS `length`, 1 AS `downloadedLength`, 0 AS `isDownloading`,\n                        `id`\n                     FROM `HanimeDownloadedEntity`\"\"\".trimIndent()\n            )\n            db.execSQL(\"\"\"DROP TABLE IF EXISTS HanimeDownloadedEntity\"\"\")\n        }\n    }\n\n    object Migration2To3 : Migration(2, 3) {\n        override fun migrate(db: SupportSQLiteDatabase) {\n            db.execSQL(\n                \"\"\"CREATE TABLE IF NOT EXISTS `DownloadCategoryEntity` (`id` INTEGER PRIMARY KEY AUTOINCREMENT NOT NULL, `name` TEXT NOT NULL)\"\"\"\n            )\n            db.execSQL(\n                \"\"\"CREATE TABLE IF NOT EXISTS `HanimeCategoryCrossRef` (`videoId` INTEGER NOT NULL, `categoryId` INTEGER NOT NULL, PRIMARY KEY(`videoId`, `categoryId`))\"\"\"\n            )\n            // Add coverUri column\n            db.execSQL(\"\"\"ALTER TABLE `HanimeDownloadEntity` ADD COLUMN `coverUri` TEXT NULL\"\"\")\n\n            // Add state column with default value (convert from isDownloading)\n            db.execSQL(\"\"\"ALTER TABLE `HanimeDownloadEntity` ADD COLUMN `state` INTEGER NOT NULL DEFAULT ${DownloadState.Mask.UNKNOWN}\"\"\")\n\n            // Update state values based on isDownloading\n            // If isDownloading=1, set state to DOWNLOADING (2)\n            // If isDownloading=0,\n            //                     if downloadedLength=length, set state to FINISHED (4)\n            //                     else set state to PAUSED (3)\n            db.execSQL(\n                \"\"\"UPDATE `HanimeDownloadEntity` SET `state` = \n                    |CASE WHEN `isDownloading` = 1 THEN ${DownloadState.Mask.DOWNLOADING} ELSE \n                    |CASE WHEN `downloadedLength` = `length` THEN ${DownloadState.Mask.FINISHED} \n                    |ELSE ${DownloadState.Mask.PAUSED} END END\"\"\".trimMargin()\n            )\n        }\n    }\n\n    object Migration3To4 : Migration(3, 4) {\n        override fun migrate(db: SupportSQLiteDatabase) {\n            db.execSQL(\n                \"\"\"CREATE TABLE IF NOT EXISTS `HUpdateEntity`(\n                    `name` TEXT NOT NULL, `url` TEXT NOT NULL,\n                    `nodeId` TEXT NOT NULL,\n                    `length` INTEGER NOT NULL, `downloadedLength` INTEGER NOT NULL, \n                    `id` INTEGER PRIMARY KEY NOT NULL)\"\"\".trimIndent()\n            )\n            db.execSQL(\"\"\"ALTER TABLE `HUpdateEntity` ADD COLUMN `state` INTEGER NOT NULL DEFAULT ${DownloadState.Mask.UNKNOWN}\"\"\")\n        }\n    }\n}"
  },
  {
    "path": "app/src/main/java/com/yenaly/han1meviewer/logic/dao/HKeyframeDao.kt",
    "content": "package com.yenaly.han1meviewer.logic.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.Update\nimport com.yenaly.han1meviewer.logic.entity.HKeyframeEntity\nimport kotlinx.coroutines.flow.Flow\n\n/**\n * @project Han1meViewer\n * @author Yenaly Liew\n * @time 2023/11/12 012 12:39\n */\n@Dao\nabstract class HKeyframeDao {\n\n    @Query(\"SELECT * FROM HKeyframeEntity ORDER BY createdTime DESC\")\n    abstract fun loadAll(): Flow<MutableList<HKeyframeEntity>>\n\n    @Query(\"SELECT * FROM HKeyframeEntity WHERE `title` LIKE '%' || :keyword || '%' OR `videoCode` == :keyword ORDER BY createdTime DESC\")\n    abstract fun loadAll(keyword: String): Flow<MutableList<HKeyframeEntity>>\n\n    @Insert(onConflict = OnConflictStrategy.REPLACE)\n    abstract suspend fun insert(entity: HKeyframeEntity)\n\n    @Update(onConflict = OnConflictStrategy.REPLACE)\n    abstract suspend fun update(entity: HKeyframeEntity)\n\n    @Delete\n    abstract suspend fun delete(entity: HKeyframeEntity)\n\n    @Query(\"SELECT * FROM HKeyframeEntity WHERE `videoCode` == :videoCode LIMIT 1\")\n    abstract suspend fun findBy(videoCode: String): HKeyframeEntity?\n\n    @Query(\"SELECT * FROM HKeyframeEntity WHERE `videoCode` == :videoCode LIMIT 1\")\n    abstract fun observe(videoCode: String): Flow<HKeyframeEntity?>\n\n    open suspend fun modifyKeyframe(\n        videoCode: String,\n        oldKeyframe: HKeyframeEntity.Keyframe, keyframe: HKeyframeEntity.Keyframe,\n    ) {\n        val entity = findBy(videoCode)\n        entity?.let {\n            // 按理説一定會有，如果沒有那就是出問題了\n            if (keyframe == oldKeyframe) return\n            removeKeyframe(videoCode, oldKeyframe)\n            appendKeyframe(videoCode, entity.title, keyframe)\n        }\n    }\n\n    open suspend fun appendKeyframe(\n        videoCode: String, title: String,\n        keyframe: HKeyframeEntity.Keyframe,\n    ) {\n        val entity = findBy(videoCode)\n        if (entity == null) {\n            insert(\n                HKeyframeEntity(\n                    videoCode,\n                    title,\n                    mutableListOf(keyframe),\n                    lastModifiedTime = System.currentTimeMillis(),\n                    createdTime = System.currentTimeMillis(),\n                    author = null\n                )\n            )\n        } else {\n            entity.keyframes += keyframe\n            entity.keyframes.sortBy { it.position }\n            update(entity.copy(lastModifiedTime = System.currentTimeMillis()))\n        }\n    }\n\n    open suspend fun removeKeyframe(\n        videoCode: String,\n        keyframe: HKeyframeEntity.Keyframe,\n    ) {\n        val entity = findBy(videoCode)\n        if (entity != null) {\n            entity.keyframes -= keyframe\n            if (entity.keyframes.isEmpty()) {\n                delete(entity)\n                return\n            }\n            update(entity.copy(lastModifiedTime = System.currentTimeMillis()))\n        }\n    }\n}"
  },
  {
    "path": "app/src/main/java/com/yenaly/han1meviewer/logic/dao/HistoryDatabase.kt",
    "content": "package com.yenaly.han1meviewer.logic.dao\n\nimport android.database.sqlite.SQLiteDatabase\nimport androidx.core.content.contentValuesOf\nimport androidx.room.Database\nimport androidx.room.Room\nimport androidx.room.RoomDatabase\nimport androidx.room.migration.Migration\nimport androidx.sqlite.db.SupportSQLiteDatabase\nimport com.yenaly.han1meviewer.logic.entity.SearchHistoryEntity\nimport com.yenaly.han1meviewer.logic.entity.WatchHistoryEntity\nimport com.yenaly.yenaly_libs.utils.applicationContext\n\n/**\n * @project Hanime1\n * @author Yenaly Liew\n * @time 2022/06/22 022 22:46\n */\n@Database(\n    entities = [SearchHistoryEntity::class, WatchHistoryEntity::class],\n    version = 2, exportSchema = false\n)\nabstract class HistoryDatabase : RoomDatabase() {\n\n    abstract val searchHistory: SearchHistoryDao\n\n    abstract val watchHistory: WatchHistoryDao\n\n    companion object {\n        val instance by lazy {\n            Room.databaseBuilder(\n                applicationContext,\n                HistoryDatabase::class.java,\n                \"history.db\"\n            ).addMigrations(Migration1To2).build()\n        }\n    }\n\n    object Migration1To2 : Migration(1, 2) {\n        override fun migrate(db: SupportSQLiteDatabase) {\n\n            val cursor = db.query(\n                \"\"\"SELECT id, redirectLink FROM WatchHistoryEntity\"\"\"\n            )\n            while (cursor.moveToNext()) {\n                val id = cursor.getInt(cursor.getColumnIndexOrThrow(\"id\"))\n                val url = cursor.getString(cursor.getColumnIndexOrThrow(\"redirectLink\"))\n                val videoCode =\n                    url.substringAfter(\"v=\") // 不用 String.toVideoCode() 的原因是，防止該拓展函數因不可抗力改變導致 migrate 失敗\n                val values = contentValuesOf(\"redirectLink\" to videoCode)\n                db.update(\n                    \"WatchHistoryEntity\",\n                    SQLiteDatabase.CONFLICT_REPLACE,\n                    values,\n                    \"id = ?\", arrayOf(id)\n                )\n            }\n            db.execSQL(\n                \"\"\"ALTER TABLE WatchHistoryEntity\n                   RENAME COLUMN redirectLink TO videoCode\"\"\"\n            )\n        }\n    }\n}\n\n\n\n"
  },
  {
    "path": "app/src/main/java/com/yenaly/han1meviewer/logic/dao/MiscellanyDatabase.kt",
    "content": "package com.yenaly.han1meviewer.logic.dao\n\nimport androidx.room.Database\nimport androidx.room.Room\nimport androidx.room.RoomDatabase\nimport com.yenaly.han1meviewer.logic.entity.HKeyframeEntity\nimport com.yenaly.yenaly_libs.utils.applicationContext\n\n/**\n * 这是各种 有数据库需求的小功能 的聚集地，\n * 如果这个功能需要数据库就放到这里。\n *\n * @project Han1meViewer\n * @author Yenaly Liew\n * @time 2023/11/12 012 12:28\n */\n@Database(\n    entities = [HKeyframeEntity::class],\n    version = 1, exportSchema = false\n)\nabstract class MiscellanyDatabase : RoomDatabase() {\n\n    abstract val hKeyframeDao: HKeyframeDao\n\n    companion object {\n        val instance by lazy {\n            Room.databaseBuilder(\n                applicationContext,\n                MiscellanyDatabase::class.java,\n                \"miscellany.db\"\n            ).build()\n        }\n    }\n}"
  },
  {
    "path": "app/src/main/java/com/yenaly/han1meviewer/logic/dao/SearchHistoryDao.kt",
    "content": "package com.yenaly.han1meviewer.logic.dao\n\nimport androidx.room.*\nimport com.yenaly.han1meviewer.logic.entity.SearchHistoryEntity\nimport kotlinx.coroutines.flow.Flow\n\n/**\n * @project Hanime1\n * @author Yenaly Liew\n * @time 2022/06/22 022 17:53\n */\n@Dao\nabstract class SearchHistoryDao {\n\n    @Query(\"SELECT * FROM SearchHistoryEntity ORDER BY id DESC\")\n    abstract fun loadAll(): Flow<MutableList<SearchHistoryEntity>>\n\n    @Query(\"SELECT * FROM SearchHistoryEntity WHERE `query` LIKE '%' || :keyword || '%' ORDER BY id DESC\")\n    abstract fun loadAll(keyword: String): Flow<MutableList<SearchHistoryEntity>>\n\n    @Delete\n    abstract suspend fun delete(history: SearchHistoryEntity)\n\n    @Insert(onConflict = OnConflictStrategy.REPLACE)\n    abstract suspend fun insert(history: SearchHistoryEntity)\n\n    @Query(\"SELECT * FROM SearchHistoryEntity WHERE (`query` = :query) LIMIT 1\")\n    abstract suspend fun find(query: String): SearchHistoryEntity?\n\n    @Query(\"DELETE FROM SearchHistoryEntity WHERE (`query` = :query)\")\n    abstract suspend fun deleteByKeyword(query: String)\n\n    @Transaction\n    open suspend fun insertOrUpdate(entity: SearchHistoryEntity) {\n        val dbEntity = find(entity.query)\n        if (dbEntity != null) {\n            delete(dbEntity)\n            insert(entity)\n            return\n        }\n        insert(entity)\n    }\n}"
  },
  {
    "path": "app/src/main/java/com/yenaly/han1meviewer/logic/dao/WatchHistoryDao.kt",
    "content": "package com.yenaly.han1meviewer.logic.dao\n\nimport androidx.room.*\nimport com.yenaly.han1meviewer.logic.entity.WatchHistoryEntity\nimport kotlinx.coroutines.flow.Flow\n\n/**\n * @project Han1meViewer\n * @author Yenaly Liew\n * @time 2022/07/02 002 16:47\n */\n@Dao\nabstract class WatchHistoryDao {\n\n    @Query(\"SELECT * FROM WatchHistoryEntity ORDER BY id DESC\")\n    abstract fun loadAll(): Flow<MutableList<WatchHistoryEntity>>\n\n    @Delete\n    abstract suspend fun delete(history: WatchHistoryEntity)\n\n    @Query(\"DELETE FROM WatchHistoryEntity\")\n    abstract suspend fun deleteAll()\n\n    @Insert(onConflict = OnConflictStrategy.REPLACE)\n    abstract suspend fun insert(history: WatchHistoryEntity)\n\n    @Update(onConflict = OnConflictStrategy.REPLACE)\n    abstract suspend fun update(history: WatchHistoryEntity)\n\n    @Query(\"SELECT * FROM WatchHistoryEntity WHERE (`videoCode` = :videoCode) LIMIT 1\")\n    abstract suspend fun findBy(videoCode: String): WatchHistoryEntity?\n\n    @Transaction\n    open suspend fun insertOrUpdate(history: WatchHistoryEntity) {\n        val dbEntity = findBy(history.videoCode)\n        if (dbEntity != null) {\n            delete(dbEntity)\n            insert(history)\n            return\n        }\n        insert(history)\n    }\n}"
  },
  {
    "path": "app/src/main/java/com/yenaly/han1meviewer/logic/dao/download/DownloadCategoryDao.kt",
    "content": "package com.yenaly.han1meviewer.logic.dao.download\n\nimport androidx.room.Dao\nimport androidx.room.Query\nimport androidx.room.RewriteQueriesToDropUnusedColumns\nimport androidx.room.Transaction\nimport com.yenaly.han1meviewer.logic.entity.download.DownloadCategoryEntity\nimport com.yenaly.han1meviewer.logic.entity.download.HanimeDownloadEntity\nimport kotlinx.coroutines.flow.Flow\n\n@Dao\nabstract class DownloadCategoryDao {\n    @RewriteQueriesToDropUnusedColumns\n    @Transaction\n    @Query(\n        \"SELECT * FROM HanimeDownloadEntity \" +\n                \"INNER JOIN HanimeCategoryCrossRef ON HanimeDownloadEntity.id = HanimeCategoryCrossRef.videoId \" +\n                \"WHERE HanimeCategoryCrossRef.categoryId = :categoryId AND HanimeDownloadEntity.downloadedLength == HanimeDownloadEntity.length\"\n    )\n    abstract fun getVideosForCategory(categoryId: Int): Flow<List<HanimeDownloadEntity>>\n\n    @Query(\"SELECT * FROM DownloadCategoryEntity\")\n    abstract fun getAllCategories(): Flow<MutableList<DownloadCategoryEntity>>\n}"
  },
  {
    "path": "app/src/main/java/com/yenaly/han1meviewer/logic/dao/download/HUpdateDao.kt",
    "content": "package com.yenaly.han1meviewer.logic.dao.download\n\nimport androidx.room.Dao\nimport androidx.room.Insert\nimport androidx.room.OnConflictStrategy\nimport androidx.room.Query\nimport androidx.room.Update\nimport com.yenaly.han1meviewer.logic.entity.download.HUpdateEntity\nimport com.yenaly.han1meviewer.logic.state.DownloadState\nimport kotlinx.coroutines.flow.Flow\n\n@Dao\nabstract class HUpdateDao {\n\n    @Query(\"SELECT * FROM HUpdateEntity WHERE state != ${DownloadState.Mask.FINISHED} AND id = 1\")\n    abstract fun loadUpdating(): Flow<HUpdateEntity>\n\n    @Query(\"DELETE FROM HUpdateEntity WHERE id = 1\")\n    abstract suspend fun delete()\n\n    @Insert(onConflict = OnConflictStrategy.Companion.REPLACE)\n    abstract suspend fun insert(entity: HUpdateEntity)\n\n    @Update(onConflict = OnConflictStrategy.Companion.REPLACE)\n    abstract suspend fun update(entity: HUpdateEntity): Int\n\n    @Query(\"SELECT * FROM HUpdateEntity WHERE id = 1\")\n    abstract suspend fun get(): HUpdateEntity?\n\n    @Query(\"UPDATE HUpdateEntity SET state = :state WHERE id = 1\")\n    abstract suspend fun updateState(state: DownloadState)\n}"
  },
  {
    "path": "app/src/main/java/com/yenaly/han1meviewer/logic/dao/download/HanimeDownloadDao.kt",
    "content": "package com.yenaly.han1meviewer.logic.dao.download\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 com.yenaly.han1meviewer.logic.entity.download.HanimeDownloadEntity\nimport com.yenaly.han1meviewer.logic.entity.download.VideoWithCategories\nimport com.yenaly.han1meviewer.logic.state.DownloadState\nimport kotlinx.coroutines.flow.Flow\n\n/**\n * @project Han1meViewer\n * @author Yenaly Liew\n * @time 2023/08/18 018 23:07\n */\n@Dao\nabstract class HanimeDownloadDao {\n    /**\n     * 获取所有未下载完成的任务\n     *\n     * 名字起得不好\n     */\n    @Query(\"SELECT * FROM HanimeDownloadEntity WHERE state != ${DownloadState.Mask.FINISHED} ORDER BY id DESC\")\n    abstract fun loadAllDownloadingHanime(): Flow<MutableList<HanimeDownloadEntity>>\n\n    /**\n     * 获取所有正在下载的任务，单次\n     */\n    //@Query(\"SELECT * FROM HanimeDownloadEntity WHERE isDownloading = 1 ORDER BY id DESC\")\n    @Query(\"SELECT * FROM HanimeDownloadEntity WHERE state != ${DownloadState.Mask.FINISHED} ORDER BY id DESC\")\n    abstract suspend fun loadAllDownloadingHanimeOnce(): MutableList<HanimeDownloadEntity>\n\n    /**\n     * 获取部分正在下载的任务，单次\n     */\n    //@Query(\"SELECT * FROM HanimeDownloadEntity WHERE isDownloading = 1 ORDER BY id DESC LIMIT :limit\")\n    @Query(\"SELECT * FROM HanimeDownloadEntity WHERE state != ${DownloadState.Mask.FINISHED} ORDER BY id DESC LIMIT :limit\")\n    abstract suspend fun loadDownloadingHanimeOnce(limit: Int): MutableList<HanimeDownloadEntity>\n\n    @Query(\n        \"SELECT * FROM HanimeDownloadEntity WHERE state = ${DownloadState.Mask.FINISHED} ORDER BY \" +\n                \"CASE WHEN :ascending THEN title END ASC, CASE WHEN NOT :ascending THEN title END DESC\"\n    )\n    @Transaction\n    abstract fun loadAllDownloadedHanimeByTitle(ascending: Boolean): Flow<MutableList<VideoWithCategories>>\n\n    @Query(\n        \"SELECT * FROM HanimeDownloadEntity WHERE state = ${DownloadState.Mask.FINISHED} ORDER BY \" +\n                \"CASE WHEN :ascending THEN id END ASC, CASE WHEN NOT :ascending THEN id END DESC\"\n    )\n    @Transaction\n    abstract fun loadAllDownloadedHanimeById(ascending: Boolean): Flow<MutableList<VideoWithCategories>>\n\n    @Query(\"DELETE FROM HanimeDownloadEntity WHERE (`videoCode` = :videoCode AND `quality` = :quality)\")\n    @Deprecated(\"查屁\")\n    abstract suspend fun delete(videoCode: String, quality: String)\n\n    @Query(\"DELETE FROM HanimeDownloadEntity WHERE (`videoCode` = :videoCode)\")\n    abstract suspend fun delete(videoCode: String)\n\n    //@Query(\"UPDATE HanimeDownloadEntity SET `isDownloading` = 0\")\n    @Query(\"UPDATE HanimeDownloadEntity SET `state` = ${DownloadState.Mask.PAUSED}\")\n    abstract suspend fun pauseAll()\n\n    @Delete\n    abstract suspend fun delete(entity: HanimeDownloadEntity)\n\n    @Insert(onConflict = OnConflictStrategy.Companion.REPLACE)\n    abstract suspend fun insert(entity: HanimeDownloadEntity)\n\n    @Update(onConflict = OnConflictStrategy.Companion.REPLACE)\n    abstract suspend fun update(entity: HanimeDownloadEntity): Int\n\n    @Query(\"SELECT * FROM HanimeDownloadEntity WHERE (`videoCode` = :videoCode AND `quality` = :quality) LIMIT 1\")\n    abstract suspend fun find(videoCode: String, quality: String): HanimeDownloadEntity?\n\n    @Query(\"SELECT * FROM HanimeDownloadEntity WHERE (`videoCode` = :videoCode) LIMIT 1\")\n    abstract suspend fun find(videoCode: String): HanimeDownloadEntity?\n\n    @Query(\"SELECT COUNT(*) FROM HanimeDownloadEntity WHERE (`videoCode` = :videoCode)\")\n    @Deprecated(\"查屁\")\n    abstract suspend fun countBy(videoCode: String): Int\n}"
  },
  {
    "path": "app/src/main/java/com/yenaly/han1meviewer/logic/entity/HKeyframeEntity.kt",
    "content": "@file:Suppress(\"PLUGIN_IS_NOT_ENABLED\")\n\npackage com.yenaly.han1meviewer.logic.entity\n\nimport androidx.room.Entity\nimport androidx.room.Ignore\nimport androidx.room.PrimaryKey\nimport androidx.room.TypeConverter\nimport androidx.room.TypeConverters\nimport com.yenaly.han1meviewer.logic.model.MultiItemEntity\nimport kotlinx.serialization.Serializable\nimport kotlinx.serialization.encodeToString\nimport kotlinx.serialization.json.Json\n\n/**\n * @project Han1meViewer\n * @author Yenaly Liew\n */\ninterface HKeyframeType : MultiItemEntity {\n    companion object {\n        const val H_KEYFRAME = 0\n        const val HEADER = 1\n    }\n}\n\n/**\n * @project Han1meViewer\n * @author Yenaly Liew\n * @time 2023/11/12 012 12:14\n */\n@Serializable\n@Entity\n@TypeConverters(HKeyframeEntity.KeyframeTypeConverter::class)\ndata class HKeyframeEntity(\n    @PrimaryKey val videoCode: String,\n    val title: String,\n    /**\n     * 关键帧列表\n     */\n    val keyframes: MutableList<Keyframe>,\n    /**\n     * 最后修改时间\n     */\n    val lastModifiedTime: Long = -1,\n    /**\n     * 创建时间\n     */\n    val createdTime: Long = -1,\n    /**\n     * 作者，null 代表本地（自己创建的），非 null 代表共享\n     */\n    val author: String? = null,\n) : HKeyframeType {\n\n    /**\n     * 分组，一个系列为一组\n     */\n    @Ignore\n    val group: String? = null\n\n    /**\n     * 所在集数\n     */\n    @Ignore\n    val episode: Int = -1\n\n    @Ignore\n    override val itemType: Int = HKeyframeType.H_KEYFRAME\n\n    @Serializable\n    data class Keyframe(\n        /**\n         * 该关键帧的时间戳\n         */\n        val position: Long,\n        /**\n         * 该关键帧的提示\n         */\n        val prompt: String?,\n    )\n\n    class KeyframeTypeConverter {\n        @TypeConverter\n        fun fromKeyframeList(keyframes: MutableList<Keyframe>): String =\n            Json.encodeToString(keyframes)\n\n        @TypeConverter\n        fun toKeyframeList(keyframes: String): MutableList<Keyframe> =\n            Json.decodeFromString(keyframes)\n    }\n}\n\n/**\n * @project Han1meViewer\n * @author Yenaly Liew\n */\ndata class HKeyframeHeader(\n    val title: String,\n    /**\n     * 附属的关键帧列表\n     */\n    val attached: List<HKeyframeEntity>,\n    override val itemType: Int = HKeyframeType.HEADER,\n) : HKeyframeType"
  },
  {
    "path": "app/src/main/java/com/yenaly/han1meviewer/logic/entity/SearchHistoryEntity.kt",
    "content": "package com.yenaly.han1meviewer.logic.entity\n\nimport androidx.room.Entity\nimport androidx.room.PrimaryKey\n\n/**\n * @project Hanime1\n * @author Yenaly Liew\n * @time 2022/06/22 022 18:16\n */\n@Entity\ndata class SearchHistoryEntity(\n    val query: String,\n    @PrimaryKey(autoGenerate = true)\n    val id: Int = 0\n)\n"
  },
  {
    "path": "app/src/main/java/com/yenaly/han1meviewer/logic/entity/WatchHistoryEntity.kt",
    "content": "package com.yenaly.han1meviewer.logic.entity\n\nimport androidx.room.Entity\nimport androidx.room.PrimaryKey\n\n/**\n * @project Han1meViewer\n * @author Yenaly Liew\n * @time 2022/07/02 002 13:13\n */\n@Entity\ndata class WatchHistoryEntity(\n    val coverUrl: String,\n    val title: String,\n    val releaseDate: Long,\n    val watchDate: Long,\n\n    val videoCode: String,\n    @PrimaryKey(autoGenerate = true)\n    val id: Int = 0,\n) {\n\n    val releaseDateDays: Int\n        get() = (releaseDate / (24 * 60 * 60 * 1000)).toInt()\n}\n"
  },
  {
    "path": "app/src/main/java/com/yenaly/han1meviewer/logic/entity/download/DownloadCategoryEntity.kt",
    "content": "package com.yenaly.han1meviewer.logic.entity.download\n\nimport androidx.room.Entity\nimport androidx.room.PrimaryKey\n\n@Entity\ndata class DownloadCategoryEntity(\n    val name: String,\n    @PrimaryKey(autoGenerate = true)\n    val id: Int = 0,\n)"
  },
  {
    "path": "app/src/main/java/com/yenaly/han1meviewer/logic/entity/download/HUpdateEntity.kt",
    "content": "package com.yenaly.han1meviewer.logic.entity.download\n\nimport androidx.annotation.IntRange\nimport androidx.room.Entity\nimport androidx.room.PrimaryKey\nimport androidx.room.TypeConverter\nimport androidx.room.TypeConverters\nimport com.yenaly.han1meviewer.logic.state.DownloadState\n\n@Entity\n@TypeConverters(HUpdateEntity.StateTypeConverter::class)\ndata class HUpdateEntity(\n    @PrimaryKey()\n    val id: Int = 1,\n\n    val name: String,\n    val url: String,\n    val nodeId: String,\n\n    /**\n     * 安装包长度\n     */\n    val length: Long,\n    /**\n     * 安装包已下载长度\n     */\n    val downloadedLength: Long,\n\n    /**\n     * 当前状态\n     */\n    val state: DownloadState = DownloadState.Unknown,\n) {\n    /**\n     * 下载进度\n     */\n    @get:IntRange(from = 0, to = 100)\n    val progress get() = (downloadedLength * 100 / length).toInt()\n    /**\n     * 是否已下载完成\n     */\n    val isDownloaded get() = state == DownloadState.Finished\n\n    val isDownloading get() = state == DownloadState.Downloading\n\n    class StateTypeConverter {\n        @TypeConverter\n        fun from(state: DownloadState): Int = state.mask\n\n        @TypeConverter\n        fun to(state: Int): DownloadState = DownloadState.from(state)\n    }\n}"
  },
  {
    "path": "app/src/main/java/com/yenaly/han1meviewer/logic/entity/download/HanimeCategoryCrossRef.kt",
    "content": "package com.yenaly.han1meviewer.logic.entity.download\n\nimport androidx.room.Entity\nimport androidx.room.Index\n\n@Entity(\n    primaryKeys = [\"videoId\", \"categoryId\"],\n    indices = [Index(value = [\"categoryId\"])],\n)\ndata class HanimeCategoryCrossRef(\n    val videoId: Int,\n    val categoryId: Int,\n)"
  },
  {
    "path": "app/src/main/java/com/yenaly/han1meviewer/logic/entity/download/HanimeDownloadEntity.kt",
    "content": "package com.yenaly.han1meviewer.logic.entity.download\n\nimport androidx.annotation.IntRange\nimport androidx.room.Entity\nimport androidx.room.PrimaryKey\nimport androidx.room.TypeConverter\nimport androidx.room.TypeConverters\nimport com.yenaly.han1meviewer.HFileManager\nimport com.yenaly.han1meviewer.logic.state.DownloadState\n\n/**\n * @project Han1meViewer\n * @author Yenaly Liew\n * @time 2023/08/18 018 21:50\n */\n@Entity\n@TypeConverters(HanimeDownloadEntity.StateTypeConverter::class)\ndata class HanimeDownloadEntity(\n    /**\n     * 封面地址\n     */\n    val coverUrl: String,\n    /**\n     * 封面图片本地地址\n     */\n    var coverUri: String?,\n    /**\n     * 影片标题\n     */\n    val title: String,\n    /**\n     * 添加日期\n     */\n    val addDate: Long,\n    /**\n     * 影片代码\n     */\n    val videoCode: String,\n    /**\n     * 影片存储在本地的位置\n     */\n    val videoUri: String,\n    /**\n     * 影片质量\n     */\n    val quality: String,\n\n    /**\n     * 影片下载地址\n     */\n    val videoUrl: String,\n    /**\n     * 影片长度\n     */\n    val length: Long,\n    /**\n     * 影片已下载长度\n     */\n    val downloadedLength: Long,\n//    /**\n//     * 是否正在下载\n//     */\n//    val isDownloading: Boolean = false,\n    /**\n     * 当前状态\n     */\n    val state: DownloadState = DownloadState.Unknown,\n\n    @PrimaryKey(autoGenerate = true)\n    val id: Int = 0,\n) {\n    /**\n     * 下载进度\n     */\n    @get:IntRange(from = 0, to = 100)\n    val progress get() = (downloadedLength * 100 / length).toInt()\n\n    /**\n     * 是否已下载完成\n     */\n    val isDownloaded get() = state == DownloadState.Finished\n\n    val isDownloading get() = state == DownloadState.Downloading\n\n    val suffix get() = videoUri.substringAfterLast(\".\", HFileManager.DEF_VIDEO_TYPE)\n\n    /**\n     * 排序方式\n     */\n    enum class SortedBy {\n        ID, TITLE\n    }\n\n    class StateTypeConverter {\n        @TypeConverter\n        fun from(state: DownloadState): Int = state.mask\n\n        @TypeConverter\n        fun to(state: Int): DownloadState = DownloadState.from(state)\n    }\n}"
  },
  {
    "path": "app/src/main/java/com/yenaly/han1meviewer/logic/entity/download/VideoWithCategories.kt",
    "content": "package com.yenaly.han1meviewer.logic.entity.download\n\nimport androidx.room.Embedded\nimport androidx.room.Junction\nimport androidx.room.Relation\n\ndata class VideoWithCategories(\n    @Embedded\n    val video: HanimeDownloadEntity,\n    @Relation(\n        parentColumn = \"id\",\n        entityColumn = \"id\",\n        associateBy = Junction(\n            value = HanimeCategoryCrossRef::class,\n            parentColumn = \"videoId\",\n            entityColumn = \"categoryId\"\n        )\n    )\n    val categories: List<DownloadCategoryEntity>,\n)\n"
  },
  {
    "path": "app/src/main/java/com/yenaly/han1meviewer/logic/exception/CloudFlareBlockedException.kt",
    "content": "package com.yenaly.han1meviewer.logic.exception\n\nimport com.yenaly.han1meviewer.R\n\n/**\n * 检测到爬虫被封鎖\n *\n * @project Han1meViewer\n * @author Yenaly Liew\n * @time 2023/08/07 007 12:45\n */\nopen class CloudFlareBlockedException(reason: String) : RuntimeException(reason) {\n    companion object {\n        val localizedMessages = intArrayOf(\n            R.string.website_blocked_msg,\n            R.string.website_blocked_msg_2,\n            R.string.website_blocked_msg_3,\n        )\n    }\n}"
  },
  {
    "path": "app/src/main/java/com/yenaly/han1meviewer/logic/exception/HanimeNotFoundException.kt",
    "content": "package com.yenaly.han1meviewer.logic.exception\n\n/**\n * @project Han1meViewer\n * @author Yenaly Liew\n * @time 2023/08/07 007 13:08\n */\nclass HanimeNotFoundException(reason: String) : RuntimeException(reason)"
  },
  {
    "path": "app/src/main/java/com/yenaly/han1meviewer/logic/exception/IPBlockedException.kt",
    "content": "package com.yenaly.han1meviewer.logic.exception\n\n/**\n * IP被封鎖\n *\n * @project Han1meViewer\n * @author Yenaly Liew\n * @time 2023/08/07 007 12:40\n */\nclass IPBlockedException(reason: String) : CloudFlareBlockedException(reason)"
  },
  {
    "path": "app/src/main/java/com/yenaly/han1meviewer/logic/exception/ParseException.kt",
    "content": "package com.yenaly.han1meviewer.logic.exception\n\n/**\n * 解析錯誤\n *\n * @project Han1meViewer\n * @author Yenaly Liew\n * @time 2023/08/05 005 16:20\n */\nclass ParseException : RuntimeException {\n\n    constructor(\n        funcName: String,\n        varName: String\n    ) : super(\"[Parse::$funcName => $varName] parse error!\")\n\n    constructor(reason: String) : super(reason)\n}"
  },
  {
    "path": "app/src/main/java/com/yenaly/han1meviewer/logic/model/HanimeInfo.kt",
    "content": "package com.yenaly.han1meviewer.logic.model\n\n/**\n * @project Han1meViewer\n * @author Yenaly Liew\n */\ninterface HanimeInfoType : MultiItemEntity\n\n/**\n * @project Han1meViewer\n * @author Yenaly Liew\n * @time 2022/06/08 008 22:56\n */\ndata class HanimeInfo(\n    val title: String,\n    val coverUrl: String,\n    val videoCode: String,\n    val duration: String? = null,\n    val uploader: String? = null,\n    val views: String? = null,\n    val uploadTime: String? = null,\n    val genre: String? = null,\n\n    val isPlaying: Boolean = false, // for video playlist only.\n\n    override var itemType: Int,\n) : HanimeInfoType {\n    companion object {\n        const val NORMAL = 0\n        const val SIMPLIFIED = 1\n    }\n}\n"
  },
  {
    "path": "app/src/main/java/com/yenaly/han1meviewer/logic/model/HanimePreview.kt",
    "content": "package com.yenaly.han1meviewer.logic.model\n\n/**\n * @project Hanime1\n * @author Yenaly Liew\n * @time 2022/06/24 024 15:05\n */\ndata class HanimePreview(\n    val headerPicUrl: String?,\n    val hasPrevious: Boolean = false,\n    val hasNext: Boolean = false,\n    val latestHanime: List<HanimeInfo>,\n    val previewInfo: List<PreviewInfo>,\n) {\n    data class PreviewInfo(\n        val title: String?,\n        val videoTitle: String?,\n        val coverUrl: String?,\n        val introduction: String?,\n        val brand: String?,\n        val releaseDate: String?,\n        val videoCode: String?,\n        val tags: List<String>,\n        val relatedPicsUrl: List<String>,\n    )\n}\n"
  },
  {
    "path": "app/src/main/java/com/yenaly/han1meviewer/logic/model/HanimeVideo.kt",
    "content": "package com.yenaly.han1meviewer.logic.model\n\nimport com.yenaly.han1meviewer.ResolutionLinkMap\nimport com.yenaly.yenaly_libs.utils.mapToArray\nimport kotlinx.datetime.LocalDate\nimport kotlinx.serialization.Serializable\nimport kotlinx.serialization.Transient\n\n/**\n * @project Hanime1\n * @author Yenaly Liew\n * @time 2022/06/11 011 20:30\n */\n@Serializable\ndata class HanimeVideo(\n    val title: String,\n    val coverUrl: String,\n    val chineseTitle: String?,\n    val introduction: String?,\n    val uploadTime: LocalDate?,\n    @Transient val views: String? = null,\n\n    // resolution to video url\n    val videoUrls: ResolutionLinkMap,\n\n    val tags: List<String>,\n    /**\n     * 注意，這裏的myList是指用戶的播放清單playlist\n     */\n    @Transient val myList: MyList? = null,\n    /**\n     * 注意，這裏的playlist是指該影片的系列影片，並非用戶的播放清單\n     */\n    @Transient val playlist: Playlist? = null,\n    @Transient val relatedHanimes: List<HanimeInfo> = emptyList(),\n    val artist: Artist? = null,\n\n    @Transient val favTimes: Int? = null,\n    @Transient val isFav: Boolean = false,\n    @Transient val csrfToken: String? = null,\n    @Transient val currentUserId: String? = null,\n) {\n\n    fun incFavTime() = copy(favTimes = favTimes?.let { it + 1 }, isFav = true)\n\n    fun decFavTime() = copy(favTimes = favTimes?.let { it - 1 }, isFav = false)\n\n    // 為保證兼容性，不能直接用天數\n    val uploadTimeMillis: Long\n        get() = uploadTime?.let {\n            it.toEpochDays().toLong() * 24 * 60 * 60 * 1000\n        } ?: 0L\n\n    data class MyList(\n        var isWatchLater: Boolean,\n        val myListInfo: List<MyListInfo>,\n    ) {\n        data class MyListInfo(\n            val code: String,\n            val title: String,\n            var isSelected: Boolean,\n        )\n\n        val titleArray get() = myListInfo.mapToArray(MyListInfo::title)\n        val isSelectedArray get() = myListInfo.map(MyListInfo::isSelected).toBooleanArray()\n    }\n\n    data class Playlist(\n        val playlistName: String?,\n        val video: List<HanimeInfo>,\n    )\n\n    @Serializable\n    data class Artist(\n        val name: String,\n        val avatarUrl: String,\n        val genre: String,\n        @Transient val post: POST? = null,\n    ) {\n        val isSubscribed: Boolean get() = post != null && post.isSubscribed\n\n        data class POST(\n            val userId: String,\n            val artistId: String,\n            val isSubscribed: Boolean,\n        )\n    }\n}"
  },
  {
    "path": "app/src/main/java/com/yenaly/han1meviewer/logic/model/HomePage.kt",
    "content": "package com.yenaly.han1meviewer.logic.model\n\n/**\n * @project Hanime1\n * @author Yenaly Liew\n * @time 2022/06/08 008 22:45\n */\ndata class HomePage(\n    val csrfToken: String?,\n    val avatarUrl: String?,\n    val username: String?,\n    val banner: Banner?,\n    val latestHanime: MutableList<HanimeInfo>,\n    val latestRelease: MutableList<HanimeInfo>,\n    val latestUpload: MutableList<HanimeInfo>,\n    val chineseSubtitle: MutableList<HanimeInfo>,\n    val hanimeTheyWatched: MutableList<HanimeInfo>,\n    val hanimeCurrent: MutableList<HanimeInfo>,\n    val hotHanimeMonthly: MutableList<HanimeInfo>,\n    // 首页TAG不想弄\n) {\n    data class Banner(\n        val title: String,\n        val description: String?,\n        val picUrl: String,\n        // 目前网站的策略是，先你吗加载广告，然后再让你跳转\n        // 我不敢保证他会把 videoCode 放在哪里，所以暂时可以为空\n        val videoCode: String?,\n    )\n}"
  },
  {
    "path": "app/src/main/java/com/yenaly/han1meviewer/logic/model/MultiItemEntity.kt",
    "content": "package com.yenaly.han1meviewer.logic.model\n\n/**\n * @project Han1meViewer\n * @author Yenaly Liew\n * @time 2023/11/26 026 17:40\n */\ninterface MultiItemEntity {\n    val itemType: Int\n}"
  },
  {
    "path": "app/src/main/java/com/yenaly/han1meviewer/logic/model/MyListItems.kt",
    "content": "package com.yenaly.han1meviewer.logic.model\n\n/**\n * @project Han1meViewer\n * @author Yenaly Liew\n * @time 2022/07/05 005 15:30\n */\ndata class MyListItems<I>(\n    val hanimeInfo: List<I>,\n    /**\n     * 清單的介紹，一般用於播放清單\n     */\n    var desc: String? = null,\n    val csrfToken: String? = null,\n)\n"
  },
  {
    "path": "app/src/main/java/com/yenaly/han1meviewer/logic/model/ParamEnum.kt",
    "content": "package com.yenaly.han1meviewer.logic.model\n\nimport com.yenaly.han1meviewer.EMPTY_STRING\n\nenum class MyListType(val value: String) {\n    FAV_VIDEO(\"LL\"),\n    WATCH_LATER(\"WL\"),\n    SUBSCRIPTION(\"SL\")\n}\n\nenum class FavStatus(val value: String) {\n    ADD_FAV(EMPTY_STRING),\n    CANCEL_FAV(\"1\")\n}\n\nenum class CommentPlace(val value: String) {\n    COMMENT(\"comment\"), // 主評論\n    CHILD_COMMENT(\"reply\") // 子評論\n}"
  },
  {
    "path": "app/src/main/java/com/yenaly/han1meviewer/logic/model/Playlists.kt",
    "content": "package com.yenaly.han1meviewer.logic.model\n\n/**\n * @project Han1meViewer\n * @author Yenaly Liew\n * @time 2023/08/26 026 17:47\n */\ndata class Playlists(\n    val playlists: List<Playlist>,\n    val csrfToken: String? = null,\n) {\n    data class Playlist(\n        val listCode: String,\n        var title: String,\n        var total: Int,\n    )\n}\n\n/**\n * 用於 修改播放清單 Flow 的返回值\n */\ndata class ModifiedPlaylistArgs(\n    var title: String,\n    var desc: String,\n    var isDeleted: Boolean,\n)\n"
  },
  {
    "path": "app/src/main/java/com/yenaly/han1meviewer/logic/model/SearchOption.kt",
    "content": "package com.yenaly.han1meviewer.logic.model\n\nimport android.os.Parcelable\nimport android.util.SparseArray\nimport androidx.core.util.valueIterator\nimport com.yenaly.han1meviewer.R\nimport com.yenaly.yenaly_libs.utils.LanguageHelper\nimport kotlinx.parcelize.Parcelize\nimport kotlinx.serialization.SerialName\nimport kotlinx.serialization.Serializable\nimport java.util.Locale\n\n@Suppress(\"EqualsOrHashCode\")\n@Serializable\n@Parcelize\ndata class SearchOption(\n    @SerialName(\"lang\")\n    val lang: Language? = null,\n    @SerialName(\"name\")\n    val name: String? = null,\n    @SerialName(\"search_key\")\n    val searchKey: String? = null,\n) : Parcelable {\n\n    companion object {\n        fun SparseArray<Set<SearchOption>>.flatten(): Set<String> = buildSet {\n            valueIterator().forEach { options ->\n                val res = options.mapNotNullTo(mutableSetOf()) { it.searchKey }\n                addAll(res)\n            }\n        }\n\n        operator fun Map<String, List<SearchOption>>.get(scopeNameRes: Int): List<SearchOption> {\n            return when (scopeNameRes) {\n                R.string.video_attr -> this[\"video_attributes\"].orEmpty()\n                R.string.relationship -> this[\"character_relationships\"].orEmpty()\n                R.string.characteristics -> this[\"characteristics\"].orEmpty()\n                R.string.appearance_and_figure -> this[\"appearance_and_figure\"].orEmpty()\n                R.string.story_plot -> this[\"story_plot\"].orEmpty()\n                R.string.sex_position -> this[\"sex_positions\"].orEmpty()\n                else -> error(\"Unknown scope name res: $scopeNameRes\")\n            }\n        }\n\n        fun toScopeKey(raw: String): Int = when (raw) {\n            \"video_attributes\" -> R.string.video_attr\n            \"character_relationships\" -> R.string.relationship\n            \"characteristics\" -> R.string.characteristics\n            \"appearance_and_figure\" -> R.string.appearance_and_figure\n            \"story_plot\" -> R.string.story_plot\n            \"sex_positions\" -> R.string.sex_position\n            else -> error(\"Unknown scope name: $raw\")\n        }\n    }\n\n    @Serializable\n    @Parcelize\n    data class Language(\n        @SerialName(\"zh-rCN\")\n        val zhrCN: String? = null,\n        @SerialName(\"zh-rTW\")\n        val zhrTW: String? = null,\n        @SerialName(\"en\")\n        val en: String? = null,\n    ) : Parcelable\n\n    override fun hashCode(): Int = searchKey.hashCode()\n\n    val value: String\n        get() = when {\n            lang == null -> name.orEmpty()\n            name == null -> LanguageHelper.preferredLanguage.let { pl ->\n                when (pl.language) {\n                    Locale.CHINESE.language -> when (pl.country) {\n                        Locale.SIMPLIFIED_CHINESE.country -> lang.zhrCN\n                        else -> lang.zhrTW\n                    }\n\n                    Locale.ENGLISH.language -> lang.en\n                    else -> lang.zhrTW\n                }\n            } ?: lang.zhrTW.orEmpty()\n\n            else -> throw IllegalArgumentException(\"Unknown lang type: ${lang.javaClass.name}\")\n        }\n}"
  },
  {
    "path": "app/src/main/java/com/yenaly/han1meviewer/logic/model/SearchTag.kt",
    "content": "package com.yenaly.han1meviewer.logic.model\n\n/**\n * @project Hanime1\n * @author Yenaly Liew\n * @time 2022/06/09 009 21:27\n */\ndata class SearchTag(\n    val genres: List<String>,\n    val tags: Map<String, List<String>>,\n    val sortOptions: List<String>,\n    val brands: List<String>,\n    val releaseDates: ReleaseDate,\n    val durationOptions: List<Pair<String, String>>,\n) {\n    data class ReleaseDate(\n        val years: List<Pair<String, String>>,\n        val months: List<Pair<String, String>>,\n    )\n}"
  },
  {
    "path": "app/src/main/java/com/yenaly/han1meviewer/logic/model/Subscription.kt",
    "content": "package com.yenaly.han1meviewer.logic.model\n\n/**\n * @since 2024/09/11\n */\ndata class Subscription(\n    val name: String,\n    val avatarUrl: String?,\n    val artistId: String?,\n    // for adapter\n    val isCheckBoxVisible: Boolean = false,\n    val isDeleteVisible: Boolean = false,\n)\n"
  },
  {
    "path": "app/src/main/java/com/yenaly/han1meviewer/logic/model/VideoComments.kt",
    "content": "package com.yenaly.han1meviewer.logic.model\n\n/**\n * @project Hanime1\n * @author Yenaly Liew\n * @time 2022/06/20 020 21:56\n */\ndata class VideoComments(\n    val videoComment: MutableList<VideoComment>,\n    val currentUserId: String? = null,\n    val csrfToken: String? = null,\n) {\n    data class VideoComment(\n        // 頭像\n        val avatar: String,\n        // 作者\n        val username: String,\n        // 發佈日期\n        val date: String,\n        // 内容\n        val content: String,\n        // 點讚數量，登入前不能憑藉post獲取\n        var thumbUp: Int? = null,\n        // 是否为子评论\n        val isChildComment: Boolean,\n        // 是否有更多回覆\n        val hasMoreReplies: Boolean = false,\n        // 評論id，登入前不能憑藉post獲取\n        val id: String? = null,\n        // post 相關\n        val post: POST,\n    ) {\n\n        val realReplyId get() = post.foreignId ?: checkNotNull(id)\n        val realLikesCount get() = thumbUp\n        fun incLikesCount(cancel: Boolean = false): VideoComment {\n            return thumbUp?.let {\n                copy(\n                    thumbUp = it + if (cancel) -1 else 1,\n                    post = post.copy(\n                        likeCommentStatus = !cancel,\n                        unlikeCommentStatus = false\n                    )\n                )\n            } ?: this\n        }\n\n        fun decLikesCount(cancel: Boolean = false): VideoComment {\n            return thumbUp?.let {\n                copy(\n                    thumbUp = it - if (cancel) -1 else 1,\n                    post = post.copy(\n                        likeCommentStatus = false,\n                        unlikeCommentStatus = !cancel\n                    )\n                )\n            } ?: this\n        }\n\n        data class POST(\n            // 對方id\n            val foreignId: String? = null,\n            // 你點的\n            var isPositive: Boolean = false,\n            // 你的id\n            val likeUserId: String? = null,\n            var commentLikesCount: Int? = null,\n            var commentLikesSum: Int? = null,\n            // 你之前有沒有點過讚\n            var likeCommentStatus: Boolean = false,\n            // 你之前有沒有點過踩\n            var unlikeCommentStatus: Boolean = false,\n        )\n    }\n}\n\n/**\n * 用於 評論交互 Flow 的返回值\n */\ndata class VideoCommentArgs(\n    // 當前評論所處adapter位置\n    val commentPosition: Int,\n    // 你當前點擊的是讚還是踩，和comment裏的isPositive不一樣\n    val isPositive: Boolean,\n    val comment: VideoComments.VideoComment,\n)"
  },
  {
    "path": "app/src/main/java/com/yenaly/han1meviewer/logic/model/github/Commit.kt",
    "content": "package com.yenaly.han1meviewer.logic.model.github\n\nimport kotlinx.serialization.Serializable\n\n@Serializable\ndata class CommitComparison(val commits: List<Commit>) {\n    @Serializable\n    data class Commit(val commit: CommitDetail) {\n        @Serializable\n        data class CommitDetail(val author: CommitAuthor, val message: String) {\n            @Serializable\n            data class CommitAuthor(val name: String)\n        }\n    }\n}\n\n\n\n"
  },
  {
    "path": "app/src/main/java/com/yenaly/han1meviewer/logic/model/github/Latest.kt",
    "content": "package com.yenaly.han1meviewer.logic.model.github\n\n/**\n * @project Han1meViewer\n * @author Yenaly Liew\n * @time 2024/03/21 021 08:56\n */\ndata class Latest(\n    val version: String,\n    val changelog: String,\n    val downloadLink: String,\n    /**\n     * Node ID for the download link\n     *\n     * 其实就是个变相的 md5 验证\n     */\n    val nodeId: String,\n)\n"
  },
  {
    "path": "app/src/main/java/com/yenaly/han1meviewer/logic/model/github/Release.kt",
    "content": "package com.yenaly.han1meviewer.logic.model.github\n\nimport android.os.Build\nimport kotlinx.serialization.SerialName\nimport kotlinx.serialization.Serializable\n\n/**\n * @project Han1meViewer\n * @author Yenaly Liew\n * @time 2022/09/09 009 21:24\n */\n@Serializable\ndata class Release(\n    val url: String,\n\n    @SerialName(\"assets_url\")\n    val assetsURL: String,\n\n    @SerialName(\"upload_url\")\n    val uploadURL: String,\n\n    @SerialName(\"html_url\")\n    val htmlURL: String,\n\n    val id: Long,\n    val author: Author,\n\n    @SerialName(\"node_id\")\n    val nodeID: String,\n\n    @SerialName(\"tag_name\")\n    val tagName: String,\n\n    @SerialName(\"target_commitish\")\n    val targetCommitish: String,\n\n    val name: String,\n    val draft: Boolean,\n    val prerelease: Boolean,\n\n    @SerialName(\"created_at\")\n    val createdAt: String,\n\n    @SerialName(\"published_at\")\n    val publishedAt: String,\n\n    val assets: List<Asset>,\n\n    @SerialName(\"tarball_url\")\n    val tarballURL: String,\n\n    @SerialName(\"zipball_url\")\n    val zipballURL: String,\n\n    val body: String,\n) {\n\n    val asset get() = assets.firstOrNull { it.name.contains(Build.SUPPORTED_ABIS[0]) }\n        ?: assets.first { it.name.contains(\"universal\") }\n\n    @Serializable\n    data class Asset(\n        val url: String,\n        val id: Long,\n\n        @SerialName(\"node_id\")\n        val nodeID: String,\n\n        val name: String,\n        val label: String? = null,\n        val uploader: Author,\n\n        @SerialName(\"content_type\")\n        val contentType: String,\n\n        val state: String,\n        val size: Long,\n\n        @SerialName(\"download_count\")\n        val downloadCount: Long,\n\n        @SerialName(\"created_at\")\n        val createdAt: String,\n\n        @SerialName(\"updated_at\")\n        val updatedAt: String,\n\n        @SerialName(\"browser_download_url\")\n        val browserDownloadURL: String,\n    )\n\n    @Serializable\n    data class Author(\n        val login: String,\n        val id: Long,\n\n        @SerialName(\"node_id\")\n        val nodeID: String,\n\n        @SerialName(\"avatar_url\")\n        val avatarURL: String,\n\n        @SerialName(\"gravatar_id\")\n        val gravatarID: String,\n\n        val url: String,\n\n        @SerialName(\"html_url\")\n        val htmlURL: String,\n\n        @SerialName(\"followers_url\")\n        val followersURL: String,\n\n        @SerialName(\"following_url\")\n        val followingURL: String,\n\n        @SerialName(\"gists_url\")\n        val gistsURL: String,\n\n        @SerialName(\"starred_url\")\n        val starredURL: String,\n\n        @SerialName(\"subscriptions_url\")\n        val subscriptionsURL: String,\n\n        @SerialName(\"organizations_url\")\n        val organizationsURL: String,\n\n        @SerialName(\"repos_url\")\n        val reposURL: String,\n\n        @SerialName(\"events_url\")\n        val eventsURL: String,\n\n        @SerialName(\"received_events_url\")\n        val receivedEventsURL: String,\n\n        val type: String,\n\n        @SerialName(\"site_admin\")\n        val siteAdmin: Boolean,\n    )\n}\n\n\n"
  },
  {
    "path": "app/src/main/java/com/yenaly/han1meviewer/logic/model/github/Workflow.kt",
    "content": "package com.yenaly.han1meviewer.logic.model.github\n\nimport android.os.Build\nimport kotlinx.serialization.SerialName\nimport kotlinx.serialization.Serializable\n\n/**\n * @project Han1meViewer\n * @author Yenaly Liew\n * @time 2024/03/21 021 09:03\n */\n@Serializable\ndata class WorkflowRuns(\n    @SerialName(\"workflow_runs\") val workflowRuns: List<WorkflowRun>,\n) {\n    @Serializable\n    data class WorkflowRun(\n        @SerialName(\"head_sha\") val headSha: String,\n        @SerialName(\"display_title\") val title: String,\n        @SerialName(\"artifacts_url\") val artifactsUrl: String,\n    )\n}\n\n\n@Serializable\ndata class Artifacts(\n    @SerialName(\"artifacts\") val artifacts: List<Artifact>,\n) {\n    val artifact get() = artifacts.firstOrNull { it.name.contains(Build.SUPPORTED_ABIS[0]) }\n        ?: artifacts.first { it.name.contains(\"universal\") }\n    val downloadLink: String get() = artifact.downloadLink\n    val nodeId: String get() = artifact.nodeId\n\n    @Serializable\n    data class Artifact(\n        val name: String,\n        @SerialName(\"archive_download_url\") val downloadLink: String,\n        @SerialName(\"node_id\") val nodeId: String,\n    )\n}"
  },
  {
    "path": "app/src/main/java/com/yenaly/han1meviewer/logic/network/GitHubDns.kt",
    "content": "package com.yenaly.han1meviewer.logic.network\n\nimport okhttp3.Dns\nimport java.net.InetAddress\n\n/**\n * @project Han1meViewer\n * @author Yenaly Liew\n * @time 2024/03/29 029 17:14\n */\nobject GitHubDns : Dns {\n    override fun lookup(hostname: String): List<InetAddress> {\n        return when (hostname) {\n            \"api.github.com\" -> listOf(InetAddress.getByName(\"140.82.121.6\"))\n            \"github.com\" -> listOf(InetAddress.getByName(\"140.82.121.4\"))\n            else -> Dns.SYSTEM.lookup(hostname)\n        }\n    }\n}"
  },
  {
    "path": "app/src/main/java/com/yenaly/han1meviewer/logic/network/HCookieJar.kt",
    "content": "package com.yenaly.han1meviewer.logic.network\n\nimport com.yenaly.han1meviewer.Preferences\nimport com.yenaly.han1meviewer.util.toLoginCookieList\nimport okhttp3.Cookie\nimport okhttp3.CookieJar\nimport okhttp3.HttpUrl\n\n/**\n * 用於管理 Cookie。\n *\n * #issue-71: 我竟然栽倒在 Cookie 管理上好幾年了！你去看我以前的管理方式，\n * 是完全錯誤的，竟然還能維持應用正常運行，太離譜了！怪不得切換簡體繁體一直不起作用！\n *\n * @project Han1meViewer\n * @author Yenaly Liew\n * @time 2024/03/13 013 15:20\n */\nclass HCookieJar : CookieJar {\n\n    companion object {\n        @JvmStatic\n        val cookieMap: MutableMap<String, MutableList<Cookie>> = mutableMapOf()\n    }\n\n    override fun loadForRequest(url: HttpUrl): List<Cookie> {\n        return cookieMap[url.host]\n            ?: Preferences.loginCookieStateFlow.value.toLoginCookieList(url.host)\n    }\n\n    override fun saveFromResponse(url: HttpUrl, cookies: List<Cookie>) {\n        cookieMap[url.host] = cookies.toMutableList().also {\n            it += Preferences.loginCookieStateFlow.value.toLoginCookieList(url.host)\n        }\n    }\n}"
  },
  {
    "path": "app/src/main/java/com/yenaly/han1meviewer/logic/network/HDns.kt",
    "content": "package com.yenaly.han1meviewer.logic.network\n\nimport android.util.Log\nimport android.widget.Toast\nimport com.yenaly.han1meviewer.HANIME_ALTER_HOSTNAME\nimport com.yenaly.han1meviewer.HANIME_MAIN_HOSTNAME\nimport com.yenaly.han1meviewer.Preferences\nimport com.yenaly.han1meviewer.R\nimport com.yenaly.yenaly_libs.utils.showShortToast\nimport kotlinx.coroutines.CoroutineScope\nimport kotlinx.coroutines.Dispatchers\nimport kotlinx.coroutines.async\nimport kotlinx.coroutines.awaitAll\nimport kotlinx.coroutines.launch\nimport kotlinx.coroutines.runBlocking\nimport kotlinx.coroutines.withContext\nimport okhttp3.Dns\nimport java.net.InetAddress\nimport java.net.InetSocketAddress\nimport java.net.Socket\n\n/**\n * @project Han1meViewer\n * @author Yenaly Liew\n * @time 2024/03/10 010 17:01\n */\nclass HDns : Dns {\n    private val scope = CoroutineScope(Dispatchers.IO)\n\n    private val dnsMap = mutableMapOf<String, List<InetAddress>>()\n    private var sortedIpsCache = emptyList<InetAddress>()\n\n    private val useBuiltInHosts = Preferences.useBuiltInHosts\n    private val isPingTest = Preferences.isPingTest\n\n    init {\n        if (useBuiltInHosts) {\n            dnsMap[HANIME_MAIN_HOSTNAME] = listOf(\n                \"104.25.254.167\", \"172.67.75.184\", \"172.64.229.154\",\n                \"2606:4700:8dd1::2a46:47f8\"\n            )\n            dnsMap[HANIME_ALTER_HOSTNAME] = listOf(\n                \"104.25.254.167\", \"172.67.75.184\", \"172.64.229.154\",\n                \"2606:4700:8dd1::2a46:47f8\"\n            )\n        }\n    }\n\n    companion object {\n\n        /**\n         * 添加DNS\n         */\n        private operator fun MutableMap<String, List<InetAddress>>.set(\n            host: String, ips: List<String>,\n        ) {\n            this[host] = ips.map {\n                InetAddress.getByAddress(host, InetAddress.getByName(it).address)\n            }\n        }\n    }\n\n    override fun lookup(hostname: String): List<InetAddress> {\n        if (sortedIpsCache.isNotEmpty()) return sortedIpsCache\n        val dns = dnsMap[hostname]\n        if (!useBuiltInHosts || dns.isNullOrEmpty()) return Dns.SYSTEM.lookup(hostname)\n        if (!isPingTest) return dns\n        CoroutineScope(Dispatchers.Main).launch {\n            showShortToast(R.string.ping_test)\n        }\n        val ipLatencies = runBlocking {\n            dns.map { ip ->\n                scope.async {\n                    ip to pingTest(ip.hostAddress!!)\n                }\n            }.awaitAll()\n        }\n        val sortedIps = ipLatencies\n            .sortedBy { (_, latency) -> latency }\n            .map { (ip, _) -> ip }\n        this.sortedIpsCache = sortedIps\n        return sortedIps\n    }\n\n\n    private suspend fun pingTest(ip: String): Long {\n        return withContext(Dispatchers.IO) {\n            try {\n                val startTime = System.currentTimeMillis()\n                Socket().use { socket ->\n                    socket.connect(InetSocketAddress(ip, 80), 300)\n                }\n                System.currentTimeMillis() - startTime\n            } catch (e: Exception) {\n                Long.MAX_VALUE\n            }\n        }\n    }\n}"
  },
  {
    "path": "app/src/main/java/com/yenaly/han1meviewer/logic/network/HProxySelector.kt",
    "content": "package com.yenaly.han1meviewer.logic.network\n\nimport com.yenaly.han1meviewer.Preferences\nimport okhttp3.internal.proxy.NullProxySelector\nimport java.io.IOException\nimport java.net.InetAddress\nimport java.net.InetSocketAddress\nimport java.net.Proxy\nimport java.net.ProxySelector\nimport java.net.SocketAddress\nimport java.net.URI\n\n/**\n * 受 [EhViewer_CN_SXJ 中 EhProxySelector](https://github.com/xiaojieonly/Ehviewer_CN_SXJ/blob/BiLi_PC_Gamer/app/src/main/java/com/hippo/ehviewer/EhProxySelector.java)\n * 的启发，Han1meViewer 也将使用 [HProxySelector] 来实现代理功能。\n *\n * @project Han1meViewer\n * @author Yenaly Liew\n * @time 2023/10/07 007 17:32\n */\n// #issue-15: 添加系统代理功能\nclass HProxySelector : ProxySelector() {\n\n    private var delegation: ProxySelector? = null\n    private val alternative: ProxySelector = getDefault() ?: NullProxySelector\n\n    init {\n        updateProxy()\n    }\n\n    companion object {\n        const val TYPE_DIRECT = 0\n        const val TYPE_SYSTEM = 1\n        const val TYPE_HTTP = 2\n        const val TYPE_SOCKS = 3\n\n        private val ipv4Regex =\n            Regex(\"^(([01]?\\\\d\\\\d?|2[0-4]\\\\d|25[0-5])\\\\.){3}([01]?\\\\d\\\\d?|2[0-4]\\\\d|25[0-5])$\")\n\n        fun validateIp(ip: String): Boolean {\n            return ipv4Regex.matches(ip)\n        }\n\n        fun validatePort(port: Int): Boolean {\n            return port in 0..65535\n        }\n\n        // #issue-39: 代理沒有應用到 WebView 上，只能通過此種方式來全局代理。\n        fun rebuildNetwork() {\n            val properties = System.getProperties()\n            when (Preferences.proxyType) {\n                TYPE_HTTP, TYPE_SOCKS -> {\n                    properties[\"proxySet\"] = true.toString()\n                    properties[\"proxyHost\"] = Preferences.proxyIp\n                    properties[\"proxyPort\"] = Preferences.proxyPort.toString()\n                }\n\n                else -> {\n                    properties[\"proxySet\"] = false.toString()\n                    properties[\"proxyHost\"] = \"\"\n                    properties[\"proxyPort\"] = \"\"\n                }\n            }\n        }\n    }\n\n    private fun updateProxy() {\n        delegation = when (Preferences.proxyType) {\n            TYPE_DIRECT -> NullProxySelector\n            TYPE_SYSTEM -> alternative\n            TYPE_HTTP, TYPE_SOCKS -> null\n            else -> NullProxySelector\n        }\n    }\n\n    override fun select(uri: URI?): MutableList<Proxy> {\n        val type = Preferences.proxyType\n        if (type == TYPE_HTTP || type == TYPE_SOCKS) {\n            val ip = Preferences.proxyIp\n            val port = Preferences.proxyPort\n            if (ip.isNotBlank() && port != -1) {\n                val inetAddress = InetAddress.getByName(ip)\n                val socketAddress = InetSocketAddress(inetAddress, port)\n                return mutableListOf(\n                    Proxy(\n                        if (type == TYPE_HTTP) Proxy.Type.HTTP else Proxy.Type.SOCKS,\n                        socketAddress\n                    )\n                )\n            }\n        }\n\n        return delegation?.select(uri) ?: alternative.select(uri)\n    }\n\n    override fun connectFailed(uri: URI?, sa: SocketAddress?, ioe: IOException?) {\n        delegation?.select(uri)\n    }\n}"
  },
  {
    "path": "app/src/main/java/com/yenaly/han1meviewer/logic/network/HUpdater.kt",
    "content": "package com.yenaly.han1meviewer.logic.network\n\nimport android.util.Log\nimport com.google.firebase.Firebase\nimport com.google.firebase.remoteconfig.remoteConfig\nimport com.yenaly.han1meviewer.BuildConfig\nimport com.yenaly.han1meviewer.FirebaseConstants\nimport com.yenaly.han1meviewer.Preferences\nimport com.yenaly.han1meviewer.logic.dao.DownloadDatabase\nimport com.yenaly.han1meviewer.logic.entity.download.HUpdateEntity\nimport com.yenaly.han1meviewer.logic.model.github.CommitComparison\nimport com.yenaly.han1meviewer.logic.model.github.Latest\nimport com.yenaly.han1meviewer.logic.state.DownloadState\nimport com.yenaly.han1meviewer.util.UPDATE_ZIP_PATH\nimport com.yenaly.han1meviewer.util.checkNeedUpdate\nimport com.yenaly.han1meviewer.util.copyTo\nimport com.yenaly.han1meviewer.util.runSuspendCatching\nimport com.yenaly.yenaly_libs.utils.applicationContext\nimport okio.use\nimport java.io.File\nimport java.io.FileOutputStream\nimport java.util.zip.ZipFile\nimport java.util.zip.ZipInputStream\n\n/**\n * @project Han1meViewer\n * @author Yenaly Liew\n * @time 2024/03/21 021 08:28\n */\nobject HUpdater {\n\n    const val TAG = \"HUpdater\"\n\n    const val DEFAULT_BRANCH = \"master\"\n\n    /**\n     * Regex to match multiple line feeds to a single line feed\n     */\n    private val linefeedRegex = Regex(\"\\\\n{2,}\")\n\n    private val hUpdateDao = DownloadDatabase.instance.hUpdateDao\n\n    /**\n     * Check for update\n     *\n     * @param forceCheck force check\n     */\n    suspend fun checkForUpdate(forceCheck: Boolean = false): Latest? {\n        // 如果未设置HA1_GITHUB_TOKEN，则不检测版本更新\n        if (BuildConfig.HA1_GITHUB_TOKEN.isEmpty()) return null\n        // debug模式不检测更新\n//        if (BuildConfig.DEBUG) return null\n        if (forceCheck || Preferences.isUpdateDialogVisible) {\n            if (Preferences.useCIUpdateChannel && Firebase.remoteConfig.getBoolean(FirebaseConstants.ENABLE_CI_UPDATE)) {\n                val curSha = BuildConfig.COMMIT_SHA\n                // 特殊情况下才用注释部分，一般情况下 branch 都是固定的，要不然多一次\n                // request 会对我的 API Token 造成负担。\n                // val apiReq = request(HA1_GITHUB_API_URL)\n                // val branch = apiReq.body?.string()?.let(::JSONObject)?.getString(\"default_branch\")\n                //     ?: return null\n                val workflowRun = HanimeNetwork.githubService.getWorkflowRuns()\n                    .workflowRuns.firstOrNull() ?: return null\n                val shortSha = workflowRun.headSha.take(7)\n                if (shortSha != curSha) {\n                    val artifacts =\n                        HanimeNetwork.githubService.getArtifacts(workflowRun.artifactsUrl)\n                    val archiveUrl = artifacts.downloadLink\n                    val nodeId = artifacts.nodeId\n                    val changelog = runSuspendCatching {\n                        HanimeNetwork.githubService.getCommitComparison(\n                            curSha = curSha,\n                            latestSha = shortSha\n                        ).commits.toChangelogPrettyString()\n                    }.getOrNull() ?: workflowRun.title\n                    return Latest(\"$shortSha (CI)\", changelog, archiveUrl, nodeId)\n                }\n            } else {\n                val ver = HanimeNetwork.githubService.getLatestVersion()\n                val isNeeded = checkNeedUpdate(ver.name)\n                if (isNeeded) {\n                    return Latest(\n                        ver.tagName, ver.body,\n                        ver.asset.browserDownloadURL,\n                        ver.asset.nodeID\n                    )\n                }\n            }\n        }\n        return null\n    }\n\n    suspend fun updateHUpdateLength(entity: HUpdateEntity, length: Long): HUpdateEntity {\n        val newEntity = entity.copy(\n            length = length\n        )\n        hUpdateDao.update(newEntity)\n        return newEntity\n    }\n    suspend fun updateHUpdateDownloadedLength(entity: HUpdateEntity, downloadedLength: Long) {\n        hUpdateDao.update(entity.copy(\n            downloadedLength = downloadedLength,\n            state = DownloadState.Downloading\n        ))\n    }\n\n    /**\n     * Inject update to file\n     *\n     * @param url update url\n     */\n    suspend fun File.injectUpdate(url: String, startOffset: Long = 0L, progress: (suspend (Int) -> Unit)? = null) {\n        var entity = hUpdateDao.get()\n        val res = HanimeNetwork.githubService.request(url, if (startOffset > 0) \"bytes=$startOffset-\" else null)\n        if (url.endsWith(\"zip\")) {\n            Log.d(TAG, \"Injecting update from zip ($url)\")\n            FileOutputStream(UPDATE_ZIP_PATH, true).use {\n                res.body()?.use { body ->\n                    val len = body.contentLength() + startOffset\n                    entity?.let { it -> entity = updateHUpdateLength(it, len) }\n                    body.byteStream().copyTo(\n                        it,\n                        len,\n                        startOffset,\n                        progress = progress,\n                        downloadLength = { length ->\n                            entity?.let { it1 ->\n                                updateHUpdateDownloadedLength(it1, length)\n                            }\n                        }\n                    )\n                }\n            }\n            ZipFile(UPDATE_ZIP_PATH).use { zip ->\n                zip.entries().asSequence().forEach { entry ->\n                    if (!entry.isDirectory) {\n                        zip.getInputStream(entry).use { input ->\n                            FileOutputStream(this, true).use { output ->\n                                input.copyTo(output)\n                            }\n                        }\n                    }\n                }\n            }\n        } else {\n            Log.d(TAG, \"Injecting update from release ($url)\")\n            FileOutputStream(this, true).use {\n                if (startOffset > 0) {\n                    it.channel.position(startOffset)\n                }\n                res.body()?.use { body ->\n                    val len = body.contentLength() + startOffset\n                    entity?.let { it -> entity = updateHUpdateLength(it, len) }\n                    body.byteStream().copyTo(\n                        it,\n                        len,\n                        startOffset,\n                        progress = progress,\n                        downloadLength = { length ->\n                            entity?.let { it1 ->\n                                updateHUpdateDownloadedLength(it1, length)\n                            }\n                        }\n                    )\n                }\n            }\n        }\n    }\n\n    /**\n     * This function is used to filter out commits that are not authored by the user.\n     */\n    private val CommitComparison.Commit.CommitDetail.CommitAuthor.isAuthorShouldIgnore: Boolean\n        get() = name.contains(\"dependabot\")\n\n    private fun List<CommitComparison.Commit>.toChangelogPrettyString(): String {\n        return filterNot { commit ->\n            commit.commit.author.isAuthorShouldIgnore\n        }.distinct().reversed().joinToString(\"\\n\\n\") { commit ->\n            val message = commit.commit.message.replace(linefeedRegex, \"\\n\")\n            \"↓ (@${commit.commit.author.name})\\n$message\"\n        }\n    }\n}"
  },
  {
    "path": "app/src/main/java/com/yenaly/han1meviewer/logic/network/HanimeNetwork.kt",
    "content": "package com.yenaly.han1meviewer.logic.network\n\nimport com.yenaly.han1meviewer.HANIME_BASE_URL\nimport com.yenaly.han1meviewer.logic.network.service.HGitHubService\nimport com.yenaly.han1meviewer.logic.network.service.HanimeBaseService\nimport com.yenaly.han1meviewer.logic.network.service.HanimeCommentService\nimport com.yenaly.han1meviewer.logic.network.service.HanimeMyListService\nimport com.yenaly.han1meviewer.logic.network.service.HanimeSubscriptionService\n\n/**\n * @project Hanime1\n * @author Yenaly Liew\n * @time 2022/06/08 008 22:35\n */\nobject HanimeNetwork {\n    var hanimeService = _hanimeService\n        private set\n    var githubService = _githubService\n        private set\n    var commentService = _commentService\n        private set\n    var myListService = _myListService\n        private set\n    var subscriptionService = _subscriptionService\n        private set\n\n    private val _hanimeService\n        get() = ServiceCreator.create<HanimeBaseService>(HANIME_BASE_URL)\n\n    private val _githubService\n        get() = ServiceCreator.createGitHubApi<HGitHubService>()\n\n    private val _commentService\n        get() = ServiceCreator.create<HanimeCommentService>(HANIME_BASE_URL)\n\n    private val _myListService\n        get() = ServiceCreator.create<HanimeMyListService>(HANIME_BASE_URL)\n\n    private val _subscriptionService\n        get() = ServiceCreator.create<HanimeSubscriptionService>(HANIME_BASE_URL)\n\n    fun rebuildNetwork() {\n        ServiceCreator.rebuildOkHttpClient()\n        hanimeService = _hanimeService\n        commentService = _commentService\n        myListService = _myListService\n    }\n}"
  },
  {
    "path": "app/src/main/java/com/yenaly/han1meviewer/logic/network/ServiceCreator.kt",
    "content": "package com.yenaly.han1meviewer.logic.network\n\nimport com.jakewharton.retrofit2.converter.kotlinx.serialization.asConverterFactory\nimport com.yenaly.han1meviewer.BuildConfig\nimport com.yenaly.han1meviewer.HA1_GITHUB_API_URL\nimport com.yenaly.han1meviewer.HJson\nimport com.yenaly.han1meviewer.Preferences\nimport com.yenaly.han1meviewer.logic.network.interceptor.SpeedLimitInterceptor\nimport com.yenaly.han1meviewer.logic.network.interceptor.UserAgentInterceptor\nimport com.yenaly.yenaly_libs.utils.applicationContext\nimport com.yenaly.yenaly_libs.utils.unsafeLazy\nimport okhttp3.Cache\nimport okhttp3.MediaType.Companion.toMediaType\nimport okhttp3.OkHttpClient\nimport retrofit2.Retrofit\nimport java.io.File\nimport java.util.concurrent.TimeUnit\n\n/**\n * @project Hanime1\n * @author Yenaly Liew\n * @time 2022/06/08 008 22:35\n */\nobject ServiceCreator {\n\n    val cache = Cache(\n        directory = File(applicationContext.cacheDir, \"http_cache\"),\n        maxSize = 10 * 1024 * 1024\n    )\n\n    private val downloadSpeedLimitInterceptor by unsafeLazy {\n        SpeedLimitInterceptor(maxSpeed = Preferences.downloadSpeedLimit)\n    }\n\n    inline fun <reified T> create(baseUrl: String): T = Retrofit.Builder()\n        .baseUrl(baseUrl)\n        .client(hClient)\n        .build()\n        .create(T::class.java)\n\n    inline fun <reified T> createGitHubApi(): T = Retrofit.Builder()\n        .baseUrl(HA1_GITHUB_API_URL)\n        .client(githubClient)\n        .addConverterFactory(HJson.asConverterFactory(\"application/json\".toMediaType()))\n        .build()\n        .create(T::class.java)\n\n    /**\n     * OkHttpClient\n     */\n    var hClient: OkHttpClient = buildHClient()\n        private set\n\n    var githubClient: OkHttpClient = buildGithubClient()\n        private set\n\n    var downloadClient: OkHttpClient = buildDownloadClient()\n        private set\n\n    /**\n     * Rebuild OkHttpClient\n     */\n    fun rebuildOkHttpClient() {\n        hClient = buildHClient()\n    }\n\n    private fun buildDownloadClient(): OkHttpClient {\n        return OkHttpClient.Builder()\n            .connectTimeout(5, TimeUnit.SECONDS)\n            .addInterceptor(UserAgentInterceptor)\n            .addInterceptor(downloadSpeedLimitInterceptor)\n            .build()\n    }\n\n    /**\n     * Build OkHttpClient\n     */\n    private fun buildHClient(): OkHttpClient {\n        return OkHttpClient.Builder()\n            .connectTimeout(15, TimeUnit.SECONDS)\n            .addInterceptor(UserAgentInterceptor)\n            .cache(cache)\n            .cookieJar(HCookieJar())\n            .proxySelector(HProxySelector())\n            .dns(HDns())\n            .build()\n    }\n\n    private fun buildGithubClient(): OkHttpClient {\n        return OkHttpClient.Builder()\n            .dns(GitHubDns)\n            .addInterceptor { chain ->\n                val request = chain.request().newBuilder().addHeader(\n                    \"Authorization\", \"Bearer ${BuildConfig.HA1_GITHUB_TOKEN}\"\n                ).build()\n                return@addInterceptor chain.proceed(request)\n            }\n            .build()\n    }\n}"
  },
  {
    "path": "app/src/main/java/com/yenaly/han1meviewer/logic/network/interceptor/SpeedLimitInterceptor.kt",
    "content": "package com.yenaly.han1meviewer.logic.network.interceptor\n\nimport okhttp3.Interceptor\nimport okhttp3.Response\n\nclass SpeedLimitInterceptor(var maxSpeed: Long) : Interceptor {\n\n    companion object {\n        const val NO_LIMIT = 0L\n\n        const val NO_LIMIT_INDEX = 0\n\n        @JvmField\n        val SPEED_BYTES = longArrayOf(\n            /* 不限速 */ 0L,\n            128 * 1024L, 256 * 1024L, 512 * 1024L,\n            1024 * 1024L, 2048 * 1024L, 4096 * 1024L,\n            8192 * 1024L, 10240 * 1024L\n        )\n    }\n\n    override fun intercept(chain: Interceptor.Chain): Response {\n        val response = chain.proceed(chain.request())\n        return response.newBuilder()\n            .body(response.body?.let {\n                SpeedLimitResponseBody(it, maxSpeed)\n            }).build()\n    }\n}"
  },
  {
    "path": "app/src/main/java/com/yenaly/han1meviewer/logic/network/interceptor/SpeedLimitResponseBody.kt",
    "content": "package com.yenaly.han1meviewer.logic.network.interceptor\n\nimport com.yenaly.yenaly_libs.utils.unsafeLazy\nimport okhttp3.ResponseBody\nimport okio.Throttler\nimport okio.buffer\n\nclass SpeedLimitResponseBody(\n    private val responseBody: ResponseBody,\n    /**\n     * 0 means no limit\n     */\n    private val maxSpeed: Long\n) : ResponseBody() {\n\n    private val throttler by unsafeLazy {\n        Throttler().apply { bytesPerSecond(maxSpeed) }\n    }\n\n    override fun contentLength(): Long = responseBody.contentLength()\n\n    override fun contentType() = responseBody.contentType()\n\n    override fun source() = throttler.source(responseBody.source()).buffer()\n}"
  },
  {
    "path": "app/src/main/java/com/yenaly/han1meviewer/logic/network/interceptor/UserAgentInterceptor.kt",
    "content": "package com.yenaly.han1meviewer.logic.network.interceptor\n\nimport com.yenaly.han1meviewer.USER_AGENT\nimport okhttp3.Interceptor\nimport okhttp3.Response\n\nobject UserAgentInterceptor : Interceptor {\n    override fun intercept(chain: Interceptor.Chain): Response {\n        val request = chain.request().newBuilder().addHeader(\n            \"User-Agent\", USER_AGENT\n        ).build()\n        return chain.proceed(request)\n    }\n}"
  },
  {
    "path": "app/src/main/java/com/yenaly/han1meviewer/logic/network/service/HGitHubService.kt",
    "content": "package com.yenaly.han1meviewer.logic.network.service\n\nimport com.yenaly.han1meviewer.logic.model.github.Artifacts\nimport com.yenaly.han1meviewer.logic.model.github.CommitComparison\nimport com.yenaly.han1meviewer.logic.model.github.Release\nimport com.yenaly.han1meviewer.logic.model.github.WorkflowRuns\nimport com.yenaly.han1meviewer.logic.network.HUpdater\nimport okhttp3.ResponseBody\nimport retrofit2.Response\nimport retrofit2.http.GET\nimport retrofit2.http.Header\nimport retrofit2.http.Path\nimport retrofit2.http.Query\nimport retrofit2.http.Url\n\n/**\n * @project Han1meViewer\n * @author Yenaly Liew\n * @time 2022/09/09 009 20:04\n */\ninterface HGitHubService {\n    @GET(\"releases/latest\")\n    suspend fun getLatestVersion(): Release\n\n    /**\n     * What is Workflow Runs?\n     *\n     * List all workflow runs for a repository. You can use parameters to filter the list of results. For example, you\n     * can get a list of workflow runs for a specific branch, or you can get a list of workflow runs that used a specific\n     * workflow file.\n     */\n    @GET(\"actions/workflows/ci.yml/runs?event=push&status=success&per_page=1\")\n    suspend fun getWorkflowRuns(\n        @Query(\"branch\") branch: String = HUpdater.DEFAULT_BRANCH,\n    ): WorkflowRuns\n\n    /**\n     * What is Commit Comparison?\n     *\n     * Compare two commits in a repository. The response will include a comparison of the two commits. The response can\n     * include difference in various aspects such as files, commits, and comments.\n     */\n    @GET(\"compare/{curSha}...{latestSha}\")\n    suspend fun getCommitComparison(\n        @Path(\"curSha\") curSha: String,\n        @Path(\"latestSha\") latestSha: String,\n    ): CommitComparison\n\n    /**\n     * What is Artifacts?\n     *\n     * Artifacts are the files produced by a workflow run. They are associated with the run during the execution of the\n     * job that produces them. Artifacts are available for 90 days after the run is completed.\n     */\n    @GET\n    suspend fun getArtifacts(\n        @Url url: String,\n    ): Artifacts\n\n    /**\n     * Typical request\n     */\n    @GET\n    suspend fun request(\n        @Url url: String,\n        @Header(\"Range\") range: String? = null\n    ): Response<ResponseBody>\n}"
  },
  {
    "path": "app/src/main/java/com/yenaly/han1meviewer/logic/network/service/HanimeBaseService.kt",
    "content": "package com.yenaly.han1meviewer.logic.network.service\n\nimport androidx.annotation.IntRange\nimport okhttp3.ResponseBody\nimport retrofit2.Response\nimport retrofit2.http.*\n\n/**\n * @project Hanime1\n * @author Yenaly Liew\n * @time 2022/06/08 008 22:10\n */\ninterface HanimeBaseService {\n\n    @GET(\"/\")\n    suspend fun getHomePage(): Response<ResponseBody>\n\n    @GET(\"search\")\n    suspend fun getHanimeSearchResult(\n        @Query(\"page\") @IntRange(from = 1) page: Int = 1,\n        @Query(\"query\") query: String? = null,\n        @Query(\"genre\") genre: String? = null,\n        @Query(\"sort\") sort: String? = null,\n        @Query(\"broad\") broad: String? = null,\n        @Query(\"date\") date: String? = null,\n        @Query(\"duration\") duration: String? = null,\n        @Query(\"tags[]\") tags: Set<String> = emptySet(),\n        @Query(\"brands[]\") brands: Set<String> = emptySet(),\n    ): Response<ResponseBody>\n\n    @GET(\"watch\")\n    suspend fun getHanimeVideo(\n        @Query(\"v\") videoCode: String,\n    ): Response<ResponseBody>\n\n    @GET(\"previews/{date}\")\n    suspend fun getHanimePreview(\n        @Path(\"date\") date: String, // 类似 202206. 202012\n    ): Response<ResponseBody>\n\n    @FormUrlEncoded\n    @POST(\"login\")\n    suspend fun login(\n        @Field(\"_token\") csrfToken: String?,\n        @Field(\"email\") email: String,\n        @Field(\"password\") password: String,\n        @Header(\"X-CSRF-TOKEN\") csrfToken_1: String? = csrfToken,\n    ): Response<ResponseBody>\n\n    @GET(\"login\")\n    suspend fun getLoginPage(): Response<ResponseBody>\n}"
  },
  {
    "path": "app/src/main/java/com/yenaly/han1meviewer/logic/network/service/HanimeCommentService.kt",
    "content": "package com.yenaly.han1meviewer.logic.network.service\n\nimport okhttp3.ResponseBody\nimport retrofit2.Response\nimport retrofit2.http.*\n\n/**\n * @project Han1meViewer\n * @author Yenaly Liew\n * @time 2022/09/19 019 17:44\n */\ninterface HanimeCommentService {\n    @GET(\"loadComment\")\n    suspend fun getComments(\n        @Query(\"type\") type: String, // 類似 \"video\", \"preview\"\n        @Query(\"id\") code: String,\n    ): Response<ResponseBody>\n\n    @GET(\"loadReplies\")\n    suspend fun getCommentReply(\n        @Query(\"id\") commentId: String,\n    ): Response<ResponseBody>\n\n    @FormUrlEncoded\n    @POST(\"createComment\")\n    suspend fun postComment(\n        @Field(\"_token\") csrfToken: String?,\n        @Field(\"comment-user-id\") currentUserId: String,\n        @Field(\"comment-type\") type: String, // 類似 \"video\", \"preview\"\n        @Field(\"comment-foreign-id\") targetUserId: String,\n        @Field(\"comment-text\") text: String,\n        @Field(\"comment-count\") count: Int = 1, // 感觉没什么用，仅前端用\n        @Field(\"comment-is-political\") isPolitical: Int = 0, // 感觉没什么用，仅前端用\n        @Header(\"X-CSRF-TOKEN\") csrfToken_1: String? = csrfToken,\n    ): Response<ResponseBody>\n\n    @FormUrlEncoded\n    @POST(\"replyComment\")\n    suspend fun postCommentReply(\n        @Field(\"_token\") csrfToken: String?,\n        @Field(\"reply-comment-id\") replyCommentId: String,\n        @Field(\"reply-comment-text\") text: String,\n        @Header(\"X-CSRF-TOKEN\") csrfToken_1: String? = csrfToken,\n    ): Response<ResponseBody>\n\n    @FormUrlEncoded\n    @POST(\"commentLike\")\n    suspend fun likeComment(\n        @Field(\"_token\") csrfToken: String?,\n        @Field(\"foreign_type\") foreignType: String,\n        @Field(\"foreign_id\") foreignId: String?,\n        @Field(\"is_positive\") isPositive: Int, // 你選擇的是讚還是踩，1是讚，0是踩\n        @Field(\"comment-like-user-id\") likeUserId: String?,\n        @Field(\"comment-likes-count\") commentLikesCount: Int,\n        @Field(\"comment-likes-sum\") commentLikesSum: Int,\n        @Field(\"like-comment-status\") likeCommentStatus: Int, // 你之前有沒有點過讚，1是0否\n        @Field(\"unlike-comment-status\") unlikeCommentStatus: Int, // 你之前有沒有點過踩，1是0否\n        @Header(\"X-CSRF-TOKEN\") csrfToken_1: String? = csrfToken,\n    ): Response<ResponseBody>\n}"
  },
  {
    "path": "app/src/main/java/com/yenaly/han1meviewer/logic/network/service/HanimeMyListService.kt",
    "content": "package com.yenaly.han1meviewer.logic.network.service\n\nimport androidx.annotation.IntRange\nimport okhttp3.ResponseBody\nimport retrofit2.Response\nimport retrofit2.http.Field\nimport retrofit2.http.FormUrlEncoded\nimport retrofit2.http.GET\nimport retrofit2.http.Header\nimport retrofit2.http.POST\nimport retrofit2.http.Path\nimport retrofit2.http.Query\n\n/**\n * MyList 是指 喜欢的影片 + 稍后再看\n *\n * Playlist 是指 自定义的播放列表\n *\n * @project Han1meViewer\n * @author Yenaly Liew\n * @time 2023/08/26 026 16:30\n */\ninterface HanimeMyListService {\n    @GET(\"playlist\")\n    suspend fun getMyListItems(\n        @Query(\"page\") @IntRange(from = 1) page: Int,\n        @Query(\"list\") typeOrCode: String, // WL, LL, SL, 12534..\n    ): Response<ResponseBody>\n\n    @FormUrlEncoded\n    @POST(\"deletePlayitem\")\n    suspend fun deleteMyListItems(\n        @Field(\"playlist_id\") listType: String,\n        @Field(\"video_id\") videoCode: String,\n        @Field(\"count\") count: Int = 1, // 隨便傳一個就行\n        @Header(\"X-CSRF-TOKEN\") csrfToken: String?,\n    ): Response<ResponseBody>\n\n    @FormUrlEncoded\n    @POST(\"like\")\n    suspend fun addToMyFavVideo(\n        @Field(\"like-foreign-id\") videoCode: String,\n        @Field(\"like-status\") likeStatus: String,\n        @Field(\"_token\") csrfToken: String?,\n        @Field(\"like-user-id\") userId: String?,\n        @Field(\"like-is-positive\") isPositive: Int = 1,\n        @Header(\"X-CSRF-TOKEN\") csrfToken_1: String? = csrfToken,\n    ): Response<ResponseBody>\n\n    @GET(\"playlists\")\n    suspend fun getPlaylists(): Response<ResponseBody>\n\n    @FormUrlEncoded\n    @POST(\"createPlaylist\")\n    suspend fun createPlaylist(\n        @Field(\"_token\") csrfToken: String?,\n        @Field(\"create-playlist-video-id\") videoCode: String,\n        @Field(\"playlist-title\") title: String,\n        @Field(\"playlist-description\") description: String,\n        @Header(\"X-CSRF-TOKEN\") csrfToken_1: String? = csrfToken,\n    ): Response<ResponseBody>\n\n    @FormUrlEncoded\n    @POST(\"save\")\n    suspend fun addToMyList(\n        @Field(\"_token\") csrfToken: String?,\n        @Field(\"input_id\") listCode: String,\n        @Field(\"video_id\") videoCode: String,\n        @Field(\"is_checked\") isChecked: Boolean,\n        @Field(\"user_id\") userId: String = \"\",\n        @Header(\"X-CSRF-TOKEN\") csrfToken_1: String? = csrfToken,\n    ): Response<ResponseBody>\n\n    @FormUrlEncoded\n    @POST(\"playlist/{list_code}\")\n    suspend fun modifyPlaylist(\n        @Path(\"list_code\") listCode: String,\n        @Field(\"playlist-title\") title: String,\n        @Field(\"playlist-description\") description: String,\n        @Field(\"playlist-delete\") delete: String?, // 删除 \"on\"，不删除 null\n        @Field(\"_token\") csrfToken: String?,\n        @Field(\"_method\") method: String? = \"PUT\",\n        @Header(\"X-CSRF-TOKEN\") csrfToken_1: String? = csrfToken,\n    ): Response<ResponseBody>\n}"
  },
  {
    "path": "app/src/main/java/com/yenaly/han1meviewer/logic/network/service/HanimeSubscriptionService.kt",
    "content": "package com.yenaly.han1meviewer.logic.network.service\n\nimport okhttp3.ResponseBody\nimport retrofit2.Response\nimport retrofit2.http.Field\nimport retrofit2.http.FormUrlEncoded\nimport retrofit2.http.Header\nimport retrofit2.http.POST\n\ninterface HanimeSubscriptionService {\n\n    @FormUrlEncoded\n    @POST(\"subscribe\")\n    suspend fun subscribeArtist(\n        @Field(\"_token\") csrfToken: String?,\n        @Field(\"subscribe-user-id\") userId: String,\n        @Field(\"subscribe-artist-id\") artistId: String,\n        // 如果当前未订阅会发送空字符串，否则发1\n        @Field(\"subscribe-status\") status: String,\n        @Header(\"X-CSRF-TOKEN\") csrfToken_1: String? = csrfToken,\n    ): Response<ResponseBody>\n}"
  },
  {
    "path": "app/src/main/java/com/yenaly/han1meviewer/logic/state/DownloadState.kt",
    "content": "package com.yenaly.han1meviewer.logic.state\n\nimport androidx.annotation.IntDef\n\n/**\n * 下载任务状态\n * @project Han1meViewer\n * @author Yenaly Liew\n * @time 2025/3/3 21:11\n */\nenum class DownloadState(@Mask val mask: Int) {\n    // 未知状态（刚添加进来的状态），队列中，下载中，暂停，已完成，失败\n    Unknown(Mask.UNKNOWN),\n    Queued(Mask.QUEUED),\n    Downloading(Mask.DOWNLOADING),\n    Paused(Mask.PAUSED),\n    Finished(Mask.FINISHED),\n    Failed(Mask.FAILED);\n\n    companion object {\n        const val STATE = \"state\"\n\n        fun from(@Mask mask: Int): DownloadState = when (mask) {\n            Mask.QUEUED -> Queued\n            Mask.DOWNLOADING -> Downloading\n            Mask.PAUSED -> Paused\n            Mask.FINISHED -> Finished\n            Mask.FAILED -> Failed\n            else -> Unknown\n        }\n    }\n\n    @IntDef(\n        Mask.UNKNOWN,\n        Mask.QUEUED,\n        Mask.DOWNLOADING,\n        Mask.PAUSED,\n        Mask.FINISHED,\n        Mask.FAILED\n    )\n    annotation class Mask {\n        companion object {\n            const val UNKNOWN = 0\n            const val QUEUED = 1\n            const val DOWNLOADING = 2\n            const val PAUSED = 3\n            const val FINISHED = 4\n            const val FAILED = 5\n        }\n    }\n}"
  },
  {
    "path": "app/src/main/java/com/yenaly/han1meviewer/logic/state/PageLoadingState.kt",
    "content": "package com.yenaly.han1meviewer.logic.state\n\n/**\n * @project Hanime1\n * @author Yenaly Liew\n * @time 2022/06/10 010 16:30\n */\nsealed class PageLoadingState<out T> {\n    data class Success<out T>(val info: T) : PageLoadingState<T>()\n    data object Loading : PageLoadingState<Nothing>()\n    data object NoMoreData : PageLoadingState<Nothing>()\n    data class Error(val throwable: Throwable) : PageLoadingState<Nothing>()\n}\n"
  },
  {
    "path": "app/src/main/java/com/yenaly/han1meviewer/logic/state/VideoLoadingState.kt",
    "content": "package com.yenaly.han1meviewer.logic.state\n\n/**\n * @project Hanime1\n * @author Yenaly Liew\n * @time 2022/06/18 018 18:14\n */\nsealed class VideoLoadingState<out T> {\n    data class Success<out T>(val info: T) : VideoLoadingState<T>()\n    data class Error(val throwable: Throwable) : VideoLoadingState<Nothing>()\n    data object Loading : VideoLoadingState<Nothing>()\n    data object NoContent : VideoLoadingState<Nothing>()\n}\n"
  },
  {
    "path": "app/src/main/java/com/yenaly/han1meviewer/logic/state/WebsiteState.kt",
    "content": "package com.yenaly.han1meviewer.logic.state\n\n/**\n * @project Hanime1\n * @author Yenaly Liew\n * @time 2022/06/08 008 22:20\n */\nsealed class WebsiteState<out T> {\n    data class Success<out T>(val info: T) : WebsiteState<T>()\n    data object Loading : WebsiteState<Nothing>()\n    data class Error(val throwable: Throwable) : WebsiteState<Nothing>()\n}\n"
  },
  {
    "path": "app/src/main/java/com/yenaly/han1meviewer/ui/StateLayoutMixin.kt",
    "content": "package com.yenaly.han1meviewer.ui\n\nimport android.annotation.SuppressLint\nimport android.widget.TextView\nimport com.drake.statelayout.StateLayout\nimport com.yenaly.han1meviewer.R\nimport com.yenaly.han1meviewer.pienization\n\n/**\n * 用於初始化 StateLayout。\n *\n * 但是不是説實現了這個接口就代表有 StateLayout 了，有些我還是直接用的 BRVAH 自帶的 StateLayoutVH，\n * 因爲用 StateLayout 稍稍有點麻煩。\n *\n * @project Han1meViewer\n * @author Yenaly Liew\n * @time 2023/11/30 030 15:24\n */\n@JvmDefaultWithoutCompatibility\ninterface StateLayoutMixin {\n\n    /**\n     * 初始化 StateLayout\n     */\n    @SuppressLint(\"SetTextI18n\")\n    fun StateLayout.init(apply: StateLayout.() -> Unit = {}) {\n        errorLayout = R.layout.layout_empty_view\n        emptyLayout = R.layout.layout_empty_view\n        onError {\n            val err = it as Throwable\n            findViewById<TextView>(R.id.tv_empty).text = err.pienization\n        }\n        apply()\n    }\n}"
  },
  {
    "path": "app/src/main/java/com/yenaly/han1meviewer/ui/activity/DownloadActivity.kt",
    "content": "package com.yenaly.han1meviewer.ui.activity\n\nimport android.os.Build\nimport android.os.Bundle\nimport android.view.LayoutInflater\nimport android.view.View\nimport androidx.core.text.parseAsHtml\nimport com.yenaly.han1meviewer.R\nimport com.yenaly.han1meviewer.databinding.ActivityDownloadBinding\nimport com.yenaly.han1meviewer.ui.fragment.home.download.DownloadedFragment\nimport com.yenaly.han1meviewer.ui.fragment.home.download.DownloadingFragment\nimport com.yenaly.han1meviewer.ui.view.funcbar.Hanidokitem\nimport com.yenaly.yenaly_libs.base.YenalyActivity\nimport com.yenaly.yenaly_libs.utils.showShortToast\nimport com.yenaly.yenaly_libs.utils.view.attach\nimport com.yenaly.yenaly_libs.utils.view.setUpFragmentStateAdapter\n\nclass DownloadActivity : YenalyActivity<ActivityDownloadBinding>() {\n\n    companion object {\n        const val TAG = \"HoppinByte\"\n\n        private const val HB = \"\"\"<span style=\"color: #FF0000;\"><b>H</b></span>oppin<b>Byte</b>\"\"\"\n        val hbSpannedTitle = HB.parseAsHtml()\n    }\n\n    private val tabNameArray = intArrayOf(R.string.downloading, R.string.downloaded)\n\n    override fun getViewBinding(layoutInflater: LayoutInflater): ActivityDownloadBinding {\n        return ActivityDownloadBinding.inflate(layoutInflater)\n    }\n\n    override fun initData(savedInstanceState: Bundle?) {\n        setSupportActionBar(binding.toolbar)\n        binding.toolbar.setNavigationOnClickListener {\n            onBackPressedDispatcher.onBackPressed()\n        }\n        supportActionBar?.let {\n            it.title = hbSpannedTitle\n            it.setDisplayHomeAsUpEnabled(true)\n            it.setHomeActionContentDescription(R.string.back)\n        }\n\n        if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.UPSIDE_DOWN_CAKE) {\n            overrideActivityTransition(\n                OVERRIDE_TRANSITION_OPEN,\n                R.anim.slide_in_from_bottom,\n                R.anim.fade_out\n            )\n            overrideActivityTransition(\n                OVERRIDE_TRANSITION_CLOSE,\n                R.anim.fade_in,\n                R.anim.slide_out_to_bottom\n            )\n        }\n\n        binding.viewPager.setUpFragmentStateAdapter(this) {\n            addFragment { DownloadingFragment() }\n            addFragment { DownloadedFragment() }\n        }\n\n        binding.tabLayout.attach(binding.viewPager) { tab, position ->\n            tab.setText(tabNameArray[position])\n        }\n\n        binding.hanidock.hanidokitems = listOf(\n            Hanidokitem.create {\n                icon = R.drawable.ic_baseline_access_time_24\n                text = R.string.title\n                viewAction = View.OnClickListener {\n                    showShortToast(\"test\")\n                }\n            },\n            Hanidokitem.create {\n                icon = R.drawable.baseline_add_24\n                text = R.string.add\n                subitems = listOf(\n                    Hanidokitem.create {\n                        icon = R.drawable.ic_baseline_access_time_24\n                        text = R.string.title\n                    }\n                )\n            }\n        )\n    }\n\n    override fun finish() {\n        super.finish()\n        if (Build.VERSION.SDK_INT < Build.VERSION_CODES.UPSIDE_DOWN_CAKE) {\n            @Suppress(\"DEPRECATION\")\n            overridePendingTransition(R.anim.fade_in, R.anim.slide_out_to_bottom)\n        }\n    }\n\n\n    override fun bindDataObservers() {\n\n    }\n}"
  },
  {
    "path": "app/src/main/java/com/yenaly/han1meviewer/ui/activity/LoginActivity.kt",
    "content": "package com.yenaly.han1meviewer.ui.activity\n\nimport android.annotation.SuppressLint\nimport android.graphics.Color\nimport android.os.Bundle\nimport android.util.Log\nimport android.view.KeyEvent\nimport android.view.View\nimport android.webkit.CookieManager\nimport android.webkit.WebResourceRequest\nimport android.webkit.WebView\nimport android.webkit.WebViewClient\nimport androidx.activity.SystemBarStyle\nimport androidx.activity.enableEdgeToEdge\nimport androidx.annotation.LayoutRes\nimport androidx.appcompat.app.AlertDialog\nimport androidx.core.text.parseAsHtml\nimport androidx.databinding.DataBindingUtil\nimport androidx.lifecycle.lifecycleScope\nimport com.google.android.material.textfield.TextInputEditText\nimport com.yenaly.han1meviewer.HANIME_ALTER_BASE_URL\nimport com.yenaly.han1meviewer.HANIME_LOGIN_URL\nimport com.yenaly.han1meviewer.HANIME_MAIN_BASE_URL\nimport com.yenaly.han1meviewer.R\nimport com.yenaly.han1meviewer.USER_AGENT\nimport com.yenaly.han1meviewer.databinding.ActivityLoginBinding\nimport com.yenaly.han1meviewer.logic.NetworkRepo\nimport com.yenaly.han1meviewer.logic.state.WebsiteState\nimport com.yenaly.han1meviewer.login\nimport com.yenaly.han1meviewer.util.createAlertDialog\nimport com.yenaly.han1meviewer.util.showWithBlurEffect\nimport com.yenaly.yenaly_libs.base.frame.FrameActivity\nimport com.yenaly.yenaly_libs.utils.showShortToast\nimport com.yenaly.yenaly_libs.utils.unsafeLazy\nimport kotlinx.coroutines.launch\n\nclass LoginActivity : FrameActivity() {\n\n    companion object {\n        const val TAG = \"LoginActivity\"\n\n        private const val HL = \"\"\"<span style=\"color: #FF0000;\"><b>H</b></span><b>a1</b>ogin\"\"\"\n        // val hlSpannedTitle = HL.parseAsHtml()\n    }\n\n    private lateinit var binding: ActivityLoginBinding\n\n    private val dialog = unsafeLazy { LoginDialog(R.layout.dialog_login) }\n\n    override fun setUiStyle() {\n        enableEdgeToEdge(\n            statusBarStyle = SystemBarStyle.dark(Color.TRANSPARENT),\n            navigationBarStyle = SystemBarStyle.dark(Color.TRANSPARENT)\n        )\n    }\n\n    override fun onCreate(savedInstanceState: Bundle?) {\n        super.onCreate(savedInstanceState)\n        binding = DataBindingUtil.setContentView(this, R.layout.activity_login)\n        setSupportActionBar(binding.toolbar)\n        binding.toolbar.setNavigationOnClickListener {\n            onBackPressedDispatcher.onBackPressed()\n        }\n        supportActionBar?.let {\n            it.title = HL.parseAsHtml()\n            it.setDisplayHomeAsUpEnabled(true)\n            it.setHomeActionContentDescription(R.string.back)\n        }\n        initWebView()\n        binding.srlLogin.setOnRefreshListener {\n            binding.wvLogin.loadUrl(HANIME_LOGIN_URL)\n        }\n        binding.srlLogin.autoRefresh()\n    }\n\n    override fun onDestroy() {\n        super.onDestroy()\n        if (dialog.isInitialized()) {\n            dialog.value.dismiss()\n        }\n        binding.wvLogin.removeAllViews()\n        binding.wvLogin.destroy()\n        binding.unbind()\n    }\n\n    override fun onKeyDown(keyCode: Int, event: KeyEvent?): Boolean {\n        if (keyCode == KeyEvent.KEYCODE_BACK && binding.wvLogin.canGoBack()) {\n            binding.wvLogin.goBack()\n            return true\n        }\n        return super.onKeyDown(keyCode, event)\n    }\n\n    @SuppressLint(\"SetJavaScriptEnabled\")\n    private fun initWebView() {\n        binding.wvLogin.apply {\n            // #issue-17: 谷歌登录需要开启JavaScript，但是谷歌拒絕這種登錄方式，遂放棄\n            settings.javaScriptEnabled = true\n            settings.domStorageEnabled = true\n            settings.userAgentString = USER_AGENT\n\n            webViewClient = object : WebViewClient() {\n                override fun onPageFinished(view: WebView, url: String) {\n                    binding.srlLogin.finishRefresh()\n                }\n\n                override fun shouldOverrideUrlLoading(\n                    view: WebView,\n                    request: WebResourceRequest,\n                ): Boolean {\n                    val isSameUrl = request.url.toString() == HANIME_MAIN_BASE_URL ||\n                            request.url.toString() == HANIME_ALTER_BASE_URL\n                    if (request.isRedirect && isSameUrl) {\n                        val url = request.url\n                        val cookieManager = CookieManager.getInstance().getCookie(url.host)\n                        Log.d(\"login_cookie\", cookieManager.toString())\n                        login(cookieManager)\n                        setResult(RESULT_OK)\n                        finish()\n                        return true\n                    }\n                    return super.shouldOverrideUrlLoading(view, request)\n                }\n\n                override fun onReceivedError(\n                    view: WebView?,\n                    errorCode: Int,\n                    description: String?,\n                    failingUrl: String?,\n                ) {\n                    // #issue-146\n                    // #issue-160: 修复字段销毁后调用引发的错误\n                    if (!isDestroyed && !isFinishing) {\n                        binding.srlLogin.finishRefresh()\n                        dialog.value.show()\n                    }\n                }\n            }\n        }\n    }\n\n    inner class LoginDialog(@LayoutRes layoutRes: Int) {\n        private val etUsername: TextInputEditText\n        private val etPassword: TextInputEditText\n\n        private val dialog: AlertDialog\n\n        private val username get() = etUsername.text?.toString().orEmpty()\n        private val password get() = etPassword.text?.toString().orEmpty()\n\n        init {\n            val view = View.inflate(this@LoginActivity, layoutRes, null)\n            etUsername = view.findViewById(R.id.et_username)\n            etPassword = view.findViewById(R.id.et_password)\n            dialog = createAlertDialog {\n                setView(view)\n                setCancelable(false)\n                setTitle(R.string.try_login_here)\n                setPositiveButton(R.string.login, null)\n                setNegativeButton(R.string.cancel, null)\n            }\n            dialog.setOnShowListener {\n                dialog.getButton(AlertDialog.BUTTON_POSITIVE).setOnClickListener {\n                    handleLogin()\n                }\n            }\n        }\n\n        private fun handleLogin() {\n            lifecycleScope.launch {\n                NetworkRepo.login(username, password).collect { state ->\n                    dialog.getButton(AlertDialog.BUTTON_POSITIVE).isEnabled =\n                        state !is WebsiteState.Loading\n                    dialog.getButton(AlertDialog.BUTTON_NEGATIVE).isEnabled =\n                        state !is WebsiteState.Loading\n                    when (state) {\n                        WebsiteState.Loading -> Unit\n\n                        is WebsiteState.Error -> {\n                            state.throwable.printStackTrace()\n                            if (state.throwable is IllegalStateException) {\n                                showShortToast(R.string.account_or_password_wrong)\n                            } else {\n                                showShortToast(R.string.login_failed)\n                            }\n                        }\n\n                        is WebsiteState.Success -> {\n                            login(state.info)\n                            setResult(RESULT_OK)\n                            dialog.dismiss()\n                            showShortToast(R.string.login_success)\n                            finish()\n                        }\n                    }\n                }\n            }\n        }\n\n        fun show() {\n            dialog.showWithBlurEffect()\n        }\n\n        fun dismiss() {\n            dialog.dismiss()\n        }\n    }\n}"
  },
  {
    "path": "app/src/main/java/com/yenaly/han1meviewer/ui/activity/MainActivity.kt",
    "content": "package com.yenaly.han1meviewer.ui.activity\n\nimport android.annotation.SuppressLint\nimport android.content.Intent\nimport android.graphics.Color\nimport android.graphics.RenderEffect\nimport android.graphics.Shader\nimport android.os.Build\nimport android.os.Bundle\nimport android.util.Log\nimport android.view.LayoutInflater\nimport android.view.View\nimport android.widget.ImageView\nimport android.widget.TextView\nimport androidx.activity.SystemBarStyle\nimport androidx.activity.enableEdgeToEdge\nimport androidx.activity.result.contract.ActivityResultContracts\nimport androidx.activity.viewModels\nimport androidx.appcompat.widget.Toolbar\nimport androidx.core.view.ViewCompat\nimport androidx.core.view.WindowInsetsCompat\nimport androidx.core.view.isVisible\nimport androidx.customview.widget.Openable\nimport androidx.drawerlayout.widget.DrawerLayout\nimport androidx.drawerlayout.widget.DrawerLayout.DrawerListener\nimport androidx.fragment.app.Fragment\nimport androidx.lifecycle.Lifecycle\nimport androidx.lifecycle.lifecycleScope\nimport androidx.lifecycle.repeatOnLifecycle\nimport androidx.navigation.NavController\nimport androidx.navigation.fragment.NavHostFragment\nimport androidx.navigation.ui.AppBarConfiguration\nimport androidx.navigation.ui.setupWithNavController\nimport coil.load\nimport coil.transform.CircleCropTransformation\nimport com.google.android.material.navigation.NavigationBarView.ACTIVE_INDICATOR_WIDTH_MATCH_PARENT\nimport com.google.android.material.navigation.NavigationView\nimport com.google.android.material.navigationrail.NavigationRailView\nimport com.google.android.material.snackbar.Snackbar\nimport com.yenaly.han1meviewer.Preferences\nimport com.yenaly.han1meviewer.Preferences.isAlreadyLogin\nimport com.yenaly.han1meviewer.R\nimport com.yenaly.han1meviewer.VIDEO_CODE\nimport com.yenaly.han1meviewer.databinding.ActivityMainBinding\nimport com.yenaly.han1meviewer.hanimeSpannedTitle\nimport com.yenaly.han1meviewer.logic.exception.CloudFlareBlockedException\nimport com.yenaly.han1meviewer.logic.state.WebsiteState\nimport com.yenaly.han1meviewer.logout\nimport com.yenaly.han1meviewer.ui.viewmodel.AppViewModel\nimport com.yenaly.han1meviewer.ui.viewmodel.MainViewModel\nimport com.yenaly.han1meviewer.util.dpToPx\nimport com.yenaly.han1meviewer.util.logScreenViewEvent\nimport com.yenaly.han1meviewer.util.showAlertDialog\nimport com.yenaly.han1meviewer.util.showUpdateDialog\nimport com.yenaly.han1meviewer.videoUrlRegex\nimport com.yenaly.yenaly_libs.base.YenalyActivity\nimport com.yenaly.yenaly_libs.utils.dp\nimport com.yenaly.yenaly_libs.utils.showShortToast\nimport com.yenaly.yenaly_libs.utils.showSnackBar\nimport com.yenaly.yenaly_libs.utils.startActivity\nimport com.yenaly.yenaly_libs.utils.textFromClipboard\nimport kotlinx.coroutines.launch\nimport kotlinx.datetime.Clock\n\n/**\n * @project Hanime1\n * @author Yenaly Liew\n * @time 2022/06/08 008 17:35\n */\nclass MainActivity : YenalyActivity<ActivityMainBinding>(), DrawerListener {\n\n    val viewModel by viewModels<MainViewModel>()\n\n    lateinit var navHostFragment: NavHostFragment\n    lateinit var navController: NavController\n\n    val currentFragment get() = navHostFragment.childFragmentManager.primaryNavigationFragment\n\n    // 登錄完了後讓activity刷新主頁\n    private val loginDataLauncher =\n        registerForActivityResult(ActivityResultContracts.StartActivityForResult()) { result ->\n            if (result.resultCode == RESULT_OK) {\n                viewModel.getHomePage()\n                initHeaderView()\n                initMenu()\n            }\n        }\n\n    override fun getViewBinding(layoutInflater: LayoutInflater): ActivityMainBinding =\n        ActivityMainBinding.inflate(layoutInflater)\n\n    override fun setUiStyle() {\n        enableEdgeToEdge(\n            statusBarStyle = SystemBarStyle.dark(Color.TRANSPARENT),\n            navigationBarStyle = SystemBarStyle.dark(Color.TRANSPARENT)\n        )\n    }\n\n    override val onFragmentResumedListener: (Fragment) -> Unit = { fragment ->\n        logScreenViewEvent(fragment)\n    }\n\n    /**\n     * 初始化数据\n     */\n    override fun initData(savedInstanceState: Bundle?) {\n        navHostFragment =\n            supportFragmentManager.findFragmentById(R.id.fcv_main) as NavHostFragment\n        navController = navHostFragment.navController\n        if (binding.nvMain is NavigationView) {\n            (binding.nvMain as NavigationView).setupWithNavController(navController)\n        }\n        if (binding.nvMain is NavigationRailView) {\n            val nvMain = binding.nvMain as NavigationRailView\n            nvMain.labelVisibilityMode = NavigationRailView.LABEL_VISIBILITY_LABELED\n            nvMain.itemActiveIndicatorExpandedWidth = ACTIVE_INDICATOR_WIDTH_MATCH_PARENT\n        }\n        if (binding.dlMain is DrawerLayout) {\n            (binding.dlMain as DrawerLayout).addDrawerListener(this)\n        }\n\n        initHeaderView()\n        initNavActivity()\n        initMenu()\n\n        ViewCompat.setOnApplyWindowInsetsListener(binding.nvMain) { v, insets ->\n            val systemBars = insets.getInsets(WindowInsetsCompat.Type.systemBars())\n            v.setPadding(systemBars.left, systemBars.top, systemBars.right, systemBars.bottom)\n            WindowInsetsCompat.CONSUMED\n        }\n    }\n\n    override fun onStart() {\n        super.onStart()\n        binding.root.post {\n            textFromClipboard?.let {\n                videoUrlRegex.find(it)?.groupValues?.get(1)?.let { videoCode ->\n                    showFindRelatedLinkSnackBar(videoCode)\n                }\n            }\n        }\n    }\n\n    override fun bindDataObservers() {\n        lifecycleScope.launch {\n            repeatOnLifecycle(Lifecycle.State.CREATED) {\n                AppViewModel.versionFlow.collect { state ->\n                    if (state is WebsiteState.Success && Preferences.isUpdateDialogVisible) {\n                        state.info?.let { release ->\n                            Preferences.lastUpdatePopupTime = Clock.System.now().epochSeconds\n                            showUpdateDialog(release)\n                        }\n                    }\n                }\n            }\n        }\n        lifecycleScope.launch {\n            repeatOnLifecycle(Lifecycle.State.CREATED) {\n                viewModel.homePageFlow.collect { state ->\n                    if (state is WebsiteState.Error) {\n                        if (state.throwable is CloudFlareBlockedException) {\n                            // TODO: 被屏蔽时的处理\n                        }\n                    }\n                }\n            }\n        }\n\n        fun setAvatar(header: View) {\n            val headerAvatar = header.findViewById<ImageView>(R.id.header_avatar)\n            val headerUsername = header.findViewById<TextView>(R.id.header_username)\n            lifecycleScope.launch {\n                repeatOnLifecycle(Lifecycle.State.CREATED) {\n                    viewModel.homePageFlow.collect { state ->\n                        if (state is WebsiteState.Success) {\n                            if (isAlreadyLogin) {\n                                if (state.info.username == null) {\n                                    headerAvatar.load(R.mipmap.ic_launcher) {\n                                        crossfade(true)\n                                        transformations(CircleCropTransformation())\n                                    }\n                                    headerUsername.setText(R.string.refresh_page_or_login_expired)\n                                } else {\n                                    headerAvatar.load(state.info.avatarUrl) {\n                                        crossfade(true)\n                                        transformations(CircleCropTransformation())\n                                    }\n                                    headerUsername.text = state.info.username\n                                }\n                            } else {\n                                initHeaderView()\n                            }\n                        } else {\n                            headerAvatar.load(R.mipmap.ic_launcher) {\n                                crossfade(true)\n                                transformations(CircleCropTransformation())\n                            }\n                            headerUsername.setText(R.string.loading)\n                        }\n                    }\n                }\n            }\n        }\n        if (binding.nvMain is NavigationView) {\n            (binding.nvMain as NavigationView).getHeaderView(0)?.let { header ->\n                setAvatar(header)\n                val collapse = header.findViewById<ImageView>(R.id.collapse)\n                collapse.isVisible = false\n            }\n        }\n        if (binding.nvMain is NavigationRailView) {\n            val nvMain = binding.nvMain as NavigationRailView\n            nvMain.headerView?.let { header ->\n                val headerAvatar = header.findViewById<ImageView>(R.id.header_avatar)\n                setAvatar(header)\n                setHeaderAvatarSize(headerAvatar, false)\n            }\n        }\n    }\n\n    override fun onSupportNavigateUp(): Boolean {\n        return navController.navigateUp() || super.onSupportNavigateUp()\n    }\n\n    override fun onDrawerSlide(drawerView: View, slideOffset: Float) {\n        if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.S) {\n            if (slideOffset > 0f) {\n                binding.fcvMain.setRenderEffect(\n                    RenderEffect.createBlurEffect(\n                        6.dp * slideOffset,\n                        6.dp * slideOffset,\n                        Shader.TileMode.CLAMP\n                    )\n                )\n            }\n        }\n    }\n\n    override fun onDrawerClosed(drawerView: View) {\n        if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.S) {\n            binding.fcvMain.setRenderEffect(null)\n        }\n    }\n\n    override fun onDrawerOpened(drawerView: View) {\n\n    }\n\n    override fun onDrawerStateChanged(newState: Int) {\n\n    }\n\n    private fun showFindRelatedLinkSnackBar(videoCode: String) {\n        showSnackBar(R.string.detect_ha1_related_link_in_clipboard, Snackbar.LENGTH_LONG) {\n            setAction(R.string.enter) {\n                startActivity<VideoActivity>(VIDEO_CODE to videoCode)\n            }\n        }\n    }\n\n    fun setHeaderAvatarSize(headerAvatar: ImageView, isExpanded: Boolean) {\n        if (isExpanded) {\n            val widthInPx = resources.getDimensionPixelSize(R.dimen.main_expanded_avatar_size)\n            val params = headerAvatar.layoutParams\n            params.width = widthInPx\n            params.height = widthInPx\n            headerAvatar.layoutParams = params\n        } else {\n            val widthInPx = resources.getDimensionPixelSize(R.dimen.main_avatar_size)\n            val params = headerAvatar.layoutParams\n            params.width = widthInPx\n            params.height = widthInPx\n            headerAvatar.layoutParams = params\n        }\n    }\n\n    @SuppressLint(\"SetTextI18n\")\n    private fun initHeaderView() {\n        fun setAvatar(view: View) {\n            val headerAvatar = view.findViewById<ImageView>(R.id.header_avatar)\n            val headerUsername = view.findViewById<TextView>(R.id.header_username)\n            if (isAlreadyLogin) {\n                headerAvatar.setOnClickListener {\n                    showAlertDialog {\n                        setTitle(R.string.sure_to_logout)\n                        setPositiveButton(R.string.sure) { _, _ ->\n                            logoutWithRefresh()\n                        }\n                        setNegativeButton(R.string.no, null)\n                    }\n                }\n            } else {\n                headerAvatar.load(R.mipmap.ic_launcher) {\n                    crossfade(true)\n                    transformations(CircleCropTransformation())\n                }\n                headerUsername.setText(R.string.not_logged_in)\n                headerAvatar.setOnClickListener {\n                    gotoLoginActivity()\n                }\n            }\n        }\n        if (binding.nvMain is NavigationView) {\n            (binding.nvMain as NavigationView).getHeaderView(0)?.let { view ->\n                setAvatar(view)\n            }\n        }\n        if (binding.nvMain is NavigationRailView) {\n            val nvMain = (binding.nvMain as NavigationRailView)\n            nvMain.headerView?.let { view ->\n                setAvatar(view)\n\n                val collapse = view.findViewById<ImageView>(R.id.collapse)\n                val headerAvatar = view.findViewById<ImageView>(R.id.header_avatar)\n                collapse.setOnClickListener {\n                    setHeaderAvatarSize(headerAvatar, !nvMain.isExpanded)\n                    if (nvMain.isExpanded) {\n                        nvMain.collapse()\n                        val params = nvMain.layoutParams\n                        params.width = 80.dpToPx()\n                        nvMain.layoutParams = params\n                    } else {\n                        nvMain.expand()\n                        val params = nvMain.layoutParams\n                        params.width = 300.dpToPx()\n                        nvMain.layoutParams = params\n                    }\n                }\n            }\n        }\n    }\n\n    // #issue-225: 侧滑选单双重点击异常，不能从 xml 里直接定义 activity 块，需要在代码里初始化\n    private fun initNavActivity() {\n        val menu = when (binding.nvMain) {\n            is NavigationView -> {\n                (binding.nvMain as NavigationView).menu\n            }\n            else -> {\n                (binding.nvMain as NavigationRailView).menu\n            }\n        }\n        menu.apply {\n            findItem(R.id.nv_settings).setOnMenuItemClickListener {\n                SettingsRouter.with(navController).toSettingsActivity()\n                return@setOnMenuItemClickListener false\n            }\n            findItem(R.id.nv_h_keyframe_settings).setOnMenuItemClickListener {\n                SettingsRouter.with(navController)\n                    .toSettingsActivity(R.id.hKeyframeSettingsFragment)\n                return@setOnMenuItemClickListener false\n            }\n            findItem(R.id.nv_download).setOnMenuItemClickListener {\n                startActivity<DownloadActivity>()\n                if (Build.VERSION.SDK_INT < Build.VERSION_CODES.UPSIDE_DOWN_CAKE) {\n                    @Suppress(\"DEPRECATION\")\n                    overridePendingTransition(R.anim.slide_in_from_bottom, R.anim.fade_out)\n                }\n                return@setOnMenuItemClickListener false\n            }\n        }\n    }\n\n    private val loginNeededFragmentList =\n        intArrayOf(R.id.nv_fav_video, R.id.nv_watch_later, R.id.nv_playlist)\n\n    private fun initMenu() {\n        val menu = when (binding.nvMain) {\n            is NavigationView -> {\n                (binding.nvMain as NavigationView).menu\n            }\n            else -> {\n                (binding.nvMain as NavigationRailView).menu\n            }\n        }\n        if (isAlreadyLogin) {\n            loginNeededFragmentList.forEach {\n                menu.findItem(it).setOnMenuItemClickListener(null)\n            }\n        } else {\n            loginNeededFragmentList.forEach {\n                menu.findItem(it).setOnMenuItemClickListener {\n                    showShortToast(R.string.login_first)\n                    return@setOnMenuItemClickListener false\n                }\n            }\n        }\n    }\n\n    private fun gotoLoginActivity() {\n        val intent = Intent(this, LoginActivity::class.java)\n        loginDataLauncher.launch(intent)\n    }\n\n    private fun logoutWithRefresh() {\n        logout()\n        initHeaderView()\n        initMenu()\n    }\n\n    /**\n     * 设置toolbar与navController关联\n     *\n     * 必须最后调用！先设置好toolbar！\n     */\n    fun Toolbar.setupWithMainNavController() {\n        supportActionBar!!.title = hanimeSpannedTitle\n        if (binding.dlMain is DrawerLayout) {\n            val appBarConfiguration = AppBarConfiguration(setOf(R.id.nv_home_page),\n                binding.dlMain as Openable?\n            )\n            this.setupWithNavController(navController, appBarConfiguration)\n        }\n    }\n}"
  },
  {
    "path": "app/src/main/java/com/yenaly/han1meviewer/ui/activity/PreviewActivity.kt",
    "content": "package com.yenaly.han1meviewer.ui.activity\n\nimport android.annotation.SuppressLint\nimport android.graphics.Color\nimport android.graphics.drawable.ColorDrawable\nimport android.os.Bundle\nimport android.view.LayoutInflater\nimport android.view.Menu\nimport android.view.MenuItem\nimport androidx.activity.SystemBarStyle\nimport androidx.activity.enableEdgeToEdge\nimport androidx.activity.viewModels\nimport androidx.annotation.OptIn\nimport androidx.core.graphics.ColorUtils\nimport androidx.core.graphics.drawable.toBitmapOrNull\nimport androidx.core.view.ViewCompat\nimport androidx.core.view.WindowInsetsCompat\nimport androidx.core.view.isGone\nimport androidx.core.view.isVisible\nimport androidx.core.view.updatePadding\nimport androidx.interpolator.view.animation.FastOutSlowInInterpolator\nimport androidx.lifecycle.Lifecycle\nimport androidx.lifecycle.lifecycleScope\nimport androidx.lifecycle.repeatOnLifecycle\nimport androidx.palette.graphics.Palette\nimport androidx.recyclerview.widget.LinearSnapHelper\nimport androidx.recyclerview.widget.RecyclerView\nimport androidx.viewpager2.widget.ViewPager2\nimport coil.imageLoader\nimport coil.load\nimport coil.request.ImageRequest\nimport com.google.android.material.appbar.AppBarLayout\nimport com.google.android.material.badge.BadgeDrawable\nimport com.google.android.material.badge.BadgeUtils\nimport com.google.android.material.badge.ExperimentalBadgeUtils\nimport com.yenaly.han1meviewer.DATE_CODE\nimport com.yenaly.han1meviewer.PREVIEW_COMMENT_PREFIX\nimport com.yenaly.han1meviewer.R\nimport com.yenaly.han1meviewer.databinding.ActivityPreviewBinding\nimport com.yenaly.han1meviewer.logic.state.WebsiteState\nimport com.yenaly.han1meviewer.pienization\nimport com.yenaly.han1meviewer.ui.adapter.HanimePreviewNewsRvAdapter\nimport com.yenaly.han1meviewer.ui.adapter.HanimePreviewTourRvAdapter\nimport com.yenaly.han1meviewer.ui.view.CenterLinearLayoutManager\nimport com.yenaly.han1meviewer.ui.viewmodel.PreviewCommentPrefetcher\nimport com.yenaly.han1meviewer.ui.viewmodel.PreviewViewModel\nimport com.yenaly.han1meviewer.util.addUpdateListener\nimport com.yenaly.han1meviewer.util.colorTransition\nimport com.yenaly.han1meviewer.util.toColorStateList\nimport com.yenaly.yenaly_libs.base.YenalyActivity\nimport com.yenaly.yenaly_libs.utils.appScreenWidth\nimport com.yenaly.yenaly_libs.utils.getThemeColor\nimport com.yenaly.yenaly_libs.utils.startActivity\nimport com.yenaly.yenaly_libs.utils.unsafeLazy\nimport com.yenaly.yenaly_libs.utils.view.AppBarLayoutStateChangeListener\nimport com.yenaly.yenaly_libs.utils.view.innerRecyclerView\nimport kotlinx.coroutines.delay\nimport kotlinx.coroutines.flow.collectLatest\nimport kotlinx.coroutines.launch\nimport kotlinx.datetime.Clock\nimport kotlinx.datetime.DateTimeUnit\nimport kotlinx.datetime.LocalDateTime\nimport kotlinx.datetime.TimeZone\nimport kotlinx.datetime.format\nimport kotlinx.datetime.format.Padding\nimport kotlinx.datetime.format.char\nimport kotlinx.datetime.minus\nimport kotlinx.datetime.plus\nimport kotlinx.datetime.toInstant\nimport kotlinx.datetime.toLocalDateTime\n\n/**\n * @project Hanime1\n * @author Yenaly Liew\n * @time 2022/06/23 023 16:46\n */\nclass PreviewActivity : YenalyActivity<ActivityPreviewBinding>() {\n\n    companion object {\n        private const val ANIM_DURATION = 300L\n        private val animInterpolator = FastOutSlowInInterpolator()\n    }\n\n    val viewModel by viewModels<PreviewViewModel>()\n\n    private val dateUtils = DateUtils()\n    private val badgeDrawable by unsafeLazy { BadgeDrawable.create(this@PreviewActivity) }\n\n    /**\n     * 左右滑动 VP 时，不触发 onScrollStateChanged，防止触发两次 binding.vpNews.setCurrentItem\n     * 导致滑动不流畅。\n     */\n    private var shouldTriggerScroll = false\n\n    private val tourSimplifiedAdapter = HanimePreviewTourRvAdapter()\n    private val linearSnapHelper = LinearSnapHelper()\n    private val newsAdapter = HanimePreviewNewsRvAdapter()\n\n    private val tourLayoutManager by unsafeLazy {\n        object : CenterLinearLayoutManager(this@PreviewActivity) {\n\n            init {\n                orientation = HORIZONTAL\n                reverseLayout = false\n            }\n\n            override fun scrollVerticallyBy(\n                dy: Int,\n                recycler: RecyclerView.Recycler?,\n                state: RecyclerView.State?,\n            ): Int {\n                if (!binding.vpNews.isInTouchMode) {\n                    onScrollWhenInNonTouchMode(dy)\n                }\n                return super.scrollVerticallyBy(dy, recycler, state)\n            }\n\n            private fun onScrollWhenInNonTouchMode(dy: Int) {\n                if (dy > 0) {\n                    binding.appBar.setExpanded(false, true)\n                } else binding.appBar.setExpanded(true, true)\n            }\n        }\n    }\n\n    override fun getViewBinding(layoutInflater: LayoutInflater): ActivityPreviewBinding =\n        ActivityPreviewBinding.inflate(layoutInflater)\n\n    override fun setUiStyle() {\n        enableEdgeToEdge(\n            statusBarStyle = SystemBarStyle.dark(Color.TRANSPARENT),\n            navigationBarStyle = SystemBarStyle.dark(Color.TRANSPARENT)\n        )\n    }\n\n    override fun initData(savedInstanceState: Bundle?) {\n        setSupportActionBar(binding.toolbar)\n        binding.toolbar.setNavigationOnClickListener {\n            onBackPressedDispatcher.onBackPressed()\n        }\n        supportActionBar?.let {\n            it.title = null\n            it.setDisplayHomeAsUpEnabled(true)\n            it.setHomeActionContentDescription(R.string.back)\n        }\n\n        PreviewCommentPrefetcher.here().tag(PreviewCommentPrefetcher.Scope.PREVIEW_ACTIVITY)\n\n        // binding.newsTitle.findViewById<TextView>(R.id.title).setText(R.string.latest_hanime_news)\n        // binding.newsTitle.findViewById<TextView>(R.id.sub_title).setText(R.string.introduction)\n\n        dateUtils.current.format(DateUtils.FORMATTED_FORMAT).let { format ->\n            viewModel.getHanimePreview(format)\n            loadComments(format)\n        }\n\n        binding.fabPrevious.setOnClickListener {\n            dateUtils.toPrevDate().format(DateUtils.FORMATTED_FORMAT).let { format ->\n                viewModel.getHanimePreview(format)\n                loadComments(format)\n            }\n        }\n        binding.fabNext.setOnClickListener {\n            dateUtils.toNextDate().format(DateUtils.FORMATTED_FORMAT).let { format ->\n                viewModel.getHanimePreview(format)\n                loadComments(format)\n            }\n        }\n\n        binding.vpNews.adapter = newsAdapter\n\n        binding.rvTourSimplified.apply {\n            layoutManager = tourLayoutManager\n            adapter = tourSimplifiedAdapter\n            clipToPadding = false\n            ViewCompat.setOnApplyWindowInsetsListener(this) { view, _ ->\n                val elementWidth = view.resources.getDimension(\n                    R.dimen.video_cover_simplified_width_small\n                )\n                val padding = appScreenWidth / 2f - elementWidth / 2f\n                view.updatePadding(left = padding.toInt(), right = padding.toInt())\n                WindowInsetsCompat.CONSUMED\n            }\n            linearSnapHelper.attachToRecyclerView(this)\n            addOnScrollListener(object : RecyclerView.OnScrollListener() {\n                override fun onScrollStateChanged(recyclerView: RecyclerView, newState: Int) {\n                    super.onScrollStateChanged(recyclerView, newState)\n                    if (newState == RecyclerView.SCROLL_STATE_IDLE) {\n                        if (shouldTriggerScroll) {\n                            val view = linearSnapHelper.findSnapView(tourLayoutManager)\n                            val position = view?.let(::getChildAdapterPosition)\n                                ?: RecyclerView.NO_POSITION\n                            binding.vpNews.setCurrentItem(position, false)\n                        }\n                        shouldTriggerScroll = true\n                    }\n                }\n            })\n        }\n\n        tourSimplifiedAdapter.setOnItemClickListener { _, _, position ->\n            binding.vpNews.setCurrentItem(position, false)\n            binding.appBar.setExpanded(false, true)\n        }\n\n        binding.vpNews.innerRecyclerView?.apply {\n            isNestedScrollingEnabled = false\n            clipToPadding = false\n            ViewCompat.setOnApplyWindowInsetsListener(this) { v, insets ->\n                val systemBars = insets.getInsets(WindowInsetsCompat.Type.navigationBars())\n                v.updatePadding(bottom = systemBars.bottom)\n                WindowInsetsCompat.CONSUMED\n            }\n        }\n        binding.vpNews.offscreenPageLimit = 1\n        binding.vpNews.registerOnPageChangeCallback(object : ViewPager2.OnPageChangeCallback() {\n            override fun onPageSelected(position: Int) {\n                shouldTriggerScroll = false\n                binding.rvTourSimplified.smoothScrollToPosition(position)\n                handleToolbarColor(position)\n            }\n        })\n\n        initAnimation()\n\n        //binding.srlPreview.setOnRefreshListener {\n        //    viewModel.getHanimePreview(dateUtils.current.second)\n        //}\n    }\n\n    @OptIn(ExperimentalBadgeUtils::class)\n    @SuppressLint(\"SetTextI18n\")\n    override fun bindDataObservers() {\n        lifecycleScope.launch {\n            repeatOnLifecycle(Lifecycle.State.CREATED) {\n                viewModel.previewFlow.collect { state ->\n                    binding.nsvPreview.isGone = state !is WebsiteState.Success\n                    binding.appBar.setExpanded(state is WebsiteState.Success, true)\n                    when (state) {\n                        is WebsiteState.Error -> {\n                            //binding.srlPreview.finishRefresh()\n                            supportActionBar?.title = state.throwable.pienization\n                        }\n\n                        is WebsiteState.Loading -> {\n                            //binding.srlPreview.autoRefresh()\n                            binding.fabPrevious.isEnabled = false\n                            binding.fabNext.isEnabled = false\n                        }\n\n                        is WebsiteState.Success -> {\n                            //binding.srlPreview.finishRefresh()\n                            binding.vpNews.setCurrentItem(0, false)\n                            supportActionBar?.title = getString(\n                                R.string.latest_hanime_list_monthly,\n                                dateUtils.current.format(DateUtils.NORMAL_FORMAT)\n                            )\n                            binding.fabPrevious.apply {\n                                isVisible = state.info.hasPrevious\n                                isEnabled = state.info.hasPrevious\n                                text = dateUtils.prevDate.format(DateUtils.NORMAL_FORMAT)\n                            }\n                            binding.fabNext.apply {\n                                isVisible = state.info.hasNext\n                                isEnabled = state.info.hasNext\n                                text = dateUtils.nextDate.format(DateUtils.NORMAL_FORMAT)\n                            }\n                            binding.cover.load(state.info.headerPicUrl) {\n                                crossfade(true)\n                                allowHardware(false)\n                                target(\n                                    onStart = binding.cover::setImageDrawable,\n                                    onError = binding.cover::setImageDrawable,\n                                    onSuccess = {\n                                        binding.cover.setImageDrawable(it)\n                                        it.toBitmapOrNull()?.let(Palette::Builder)?.generate { p ->\n                                            p?.let(::handleHeaderPalette)\n                                        }\n                                    }\n                                )\n                            }\n                            tourSimplifiedAdapter.submitList(state.info.latestHanime)\n                            // 有時候 tour 無法順利加載出來，加點延遲反而就好了，哈哈\n                            delay(100)\n                            handleToolbarColor(0)\n                            newsAdapter.submitList(state.info.previewInfo)\n                        }\n                    }\n                }\n            }\n        }\n\n        lifecycleScope.launch {\n            PreviewCommentPrefetcher.here().commentFlow.collectLatest { comments ->\n                badgeDrawable.apply {\n                    isVisible = comments.isNotEmpty()\n                    number = comments.size\n                    badgeGravity = BadgeDrawable.TOP_START\n                }\n                BadgeUtils.attachBadgeDrawable(badgeDrawable, binding.toolbar, R.id.tb_comment)\n            }\n        }\n    }\n\n    override fun onDestroy() {\n        super.onDestroy()\n        PreviewCommentPrefetcher.bye(PreviewCommentPrefetcher.Scope.PREVIEW_ACTIVITY)\n    }\n\n    override fun onCreateOptionsMenu(menu: Menu): Boolean {\n        menuInflater.inflate(R.menu.menu_preview_toolbar, menu)\n        return super.onCreateOptionsMenu(menu)\n    }\n\n    override fun onOptionsItemSelected(item: MenuItem): Boolean {\n        when (item.itemId) {\n            R.id.tb_comment -> {\n                startActivity<PreviewCommentActivity>(\n                    \"date\" to dateUtils.current.format(DateUtils.NORMAL_FORMAT),\n                    DATE_CODE to dateUtils.current.format(DateUtils.FORMATTED_FORMAT)\n                )\n            }\n        }\n        return super.onOptionsItemSelected(item)\n    }\n\n    private fun loadComments(currentFormat: String) {\n        PreviewCommentPrefetcher.here()\n            .fetch(PREVIEW_COMMENT_PREFIX, currentFormat)\n    }\n\n    private fun handleToolbarColor(index: Int) {\n        val data = tourSimplifiedAdapter.getItem(index)?.coverUrl\n        val request = ImageRequest.Builder(this)\n            .data(data)\n            .allowHardware(false)\n            .target(\n                onError = {\n\n                },\n                onSuccess = {\n                    it.toBitmapOrNull()?.let(Palette::Builder)?.generate { p ->\n                        p?.let(::handleToolbarPalette)\n                    }\n                }\n            )\n            .build()\n        this.imageLoader.enqueue(request)\n    }\n\n    private fun handleToolbarPalette(p: Palette) {\n        val darkMuted =\n            p.darkMutedSwatch?.rgb ?: p.darkVibrantSwatch?.rgb ?: p.lightVibrantSwatch?.rgb\n            ?: p.lightMutedSwatch?.rgb ?: Color.BLACK\n        colorTransition(\n            fromColor = (binding.collapsingToolbar.contentScrim as ColorDrawable).color,\n            toColor = ColorUtils.blendARGB(darkMuted, Color.BLACK, 0.3f)\n        ) {\n            duration = ANIM_DURATION\n            interpolator = animInterpolator\n            addUpdateListener(lifecycle) {\n                val value = it.animatedValue as Int\n                binding.collapsingToolbar.setContentScrimColor(value)\n            }\n        }\n        colorTransition(\n            fromColor = (binding.llTour.background as? ColorDrawable)?.color ?: Color.TRANSPARENT,\n            toColor = darkMuted\n        ) {\n            duration = ANIM_DURATION\n            interpolator = animInterpolator\n            addUpdateListener(lifecycle) {\n                val value = it.animatedValue as Int\n                binding.llTour.setBackgroundColor(value)\n            }\n        }\n    }\n\n    private fun handleHeaderPalette(p: Palette) {\n        val colorPrimary = getThemeColor(com.google.android.material.R.attr.colorPrimaryFixed)\n        val lightVibrant = p.getLightVibrantColor(colorPrimary)\n        val per70lightVibrantStateList =\n            ColorUtils.setAlphaComponent(lightVibrant, 0xB3).toColorStateList()\n        binding.fabPrevious.backgroundTintList = per70lightVibrantStateList\n        binding.fabNext.backgroundTintList = per70lightVibrantStateList\n        val titleTextColorStateList =\n            (p.lightVibrantSwatch?.titleTextColor ?: Color.BLACK).toColorStateList()\n        binding.fabPrevious.iconTint = titleTextColorStateList\n        binding.fabNext.iconTint = titleTextColorStateList\n        binding.fabPrevious.setTextColor(titleTextColorStateList)\n        binding.fabNext.setTextColor(titleTextColorStateList)\n    }\n\n    private fun initAnimation() {\n        binding.appBar.addOnOffsetChangedListener(object : AppBarLayoutStateChangeListener() {\n            override fun onStateChanged(appBarLayout: AppBarLayout, state: State) {\n                when (state) {\n                    State.EXPANDED -> {\n                        binding.fabPrevious.animate().translationX(0F).setDuration(ANIM_DURATION)\n                            .setInterpolator(animInterpolator).start()\n                        binding.fabNext.animate().translationX(0F).setDuration(ANIM_DURATION)\n                            .setInterpolator(animInterpolator).start()\n                    }\n\n                    State.INTERMEDIATE -> {\n                        binding.fabPrevious.animate().translationX(-500F).setDuration(ANIM_DURATION)\n                            .setInterpolator(animInterpolator).start()\n                        binding.fabNext.animate().translationX(500F).setDuration(ANIM_DURATION)\n                            .setInterpolator(animInterpolator).start()\n                    }\n\n                    State.COLLAPSED -> {\n\n                    }\n                }\n            }\n        })\n    }\n\n    /**\n     * 单纯给这个用的DateUtils\n     */\n    private class DateUtils {\n\n        companion object {\n            /**\n             * 2022/2\n             */\n            val NORMAL_FORMAT = LocalDateTime.Format {\n                year(); char('/'); monthNumber(Padding.NONE)\n            }\n\n            /**\n             * 202202\n             */\n            val FORMATTED_FORMAT = LocalDateTime.Format {\n                year(); monthNumber()\n            }\n        }\n\n        // 當前顯示的日期\n        var current: LocalDateTime = currentDate\n            private set\n\n        val currentDate: LocalDateTime\n            get() = Clock.System.now().toLocalDateTime(TimeZone.currentSystemDefault())\n\n        val prevDate: LocalDateTime\n            get() {\n                val instant = current.toInstant(TimeZone.currentSystemDefault())\n                return instant.minus(1, DateTimeUnit.MONTH, TimeZone.currentSystemDefault())\n                    .toLocalDateTime(TimeZone.currentSystemDefault())\n            }\n\n        val nextDate: LocalDateTime\n            get() {\n                val instant = current.toInstant(TimeZone.currentSystemDefault())\n                return instant.plus(1, DateTimeUnit.MONTH, TimeZone.currentSystemDefault())\n                    .toLocalDateTime(TimeZone.currentSystemDefault())\n            }\n\n        fun toPrevDate(): LocalDateTime {\n            current = prevDate\n            return current\n        }\n\n        fun toNextDate(): LocalDateTime {\n            current = nextDate\n            return current\n        }\n    }\n}"
  },
  {
    "path": "app/src/main/java/com/yenaly/han1meviewer/ui/activity/PreviewCommentActivity.kt",
    "content": "package com.yenaly.han1meviewer.ui.activity\n\nimport android.graphics.Color\nimport android.os.Bundle\nimport android.view.LayoutInflater\nimport androidx.activity.SystemBarStyle\nimport androidx.activity.enableEdgeToEdge\nimport androidx.activity.viewModels\nimport com.yenaly.han1meviewer.COMMENT_TYPE\nimport com.yenaly.han1meviewer.DATE_CODE\nimport com.yenaly.han1meviewer.PREVIEW_COMMENT_PREFIX\nimport com.yenaly.han1meviewer.R\nimport com.yenaly.han1meviewer.databinding.ActivityPreviewCommentBinding\nimport com.yenaly.han1meviewer.ui.fragment.video.CommentFragment\nimport com.yenaly.han1meviewer.ui.viewmodel.CommentViewModel\nimport com.yenaly.han1meviewer.ui.viewmodel.PreviewCommentPrefetcher\nimport com.yenaly.yenaly_libs.base.YenalyActivity\nimport com.yenaly.yenaly_libs.utils.makeBundle\nimport com.yenaly.yenaly_libs.utils.safeIntentExtra\n\n/**\n * @project Han1meViewer\n * @author Yenaly Liew\n * @time 2022/06/28 028 12:03\n */\nclass PreviewCommentActivity : YenalyActivity<ActivityPreviewCommentBinding>() {\n\n    val viewModel by viewModels<CommentViewModel>()\n\n    private val date by safeIntentExtra<String>(\"date\") // 感覺沒必要弄成個常量\n    private val dateCode by safeIntentExtra<String>(DATE_CODE)\n\n    override fun getViewBinding(layoutInflater: LayoutInflater): ActivityPreviewCommentBinding =\n        ActivityPreviewCommentBinding.inflate(layoutInflater)\n\n    override fun setUiStyle() {\n        enableEdgeToEdge(\n            statusBarStyle = SystemBarStyle.dark(Color.TRANSPARENT),\n            navigationBarStyle = SystemBarStyle.dark(Color.TRANSPARENT)\n        )\n    }\n\n    override fun initData(savedInstanceState: Bundle?) {\n        setSupportActionBar(binding.toolbar)\n        binding.toolbar.setNavigationOnClickListener {\n            onBackPressedDispatcher.onBackPressed()\n        }\n        supportActionBar?.let {\n            it.setDisplayHomeAsUpEnabled(true)\n            it.setHomeActionContentDescription(R.string.back)\n            it.title = getString(R.string.latest_hanime_comment, date)\n        }\n\n        viewModel.code = dateCode\n\n        PreviewCommentPrefetcher.here().tag(PreviewCommentPrefetcher.Scope.PREVIEW_COMMENT_ACTIVITY)\n\n        val commentFragment =\n            CommentFragment().makeBundle(COMMENT_TYPE to PREVIEW_COMMENT_PREFIX)\n        supportFragmentManager.beginTransaction().apply {\n            add(R.id.fcv_pre_comment, commentFragment)\n        }.commit()\n    }\n\n    override fun onDestroy() {\n        super.onDestroy()\n        PreviewCommentPrefetcher.bye(PreviewCommentPrefetcher.Scope.PREVIEW_COMMENT_ACTIVITY)\n    }\n}"
  },
  {
    "path": "app/src/main/java/com/yenaly/han1meviewer/ui/activity/SearchActivity.kt",
    "content": "package com.yenaly.han1meviewer.ui.activity\n\nimport android.annotation.SuppressLint\nimport android.content.res.Configuration\nimport android.graphics.Color\nimport android.os.Bundle\nimport android.util.Log\nimport android.view.LayoutInflater\nimport android.view.View\nimport android.view.ViewGroup.MarginLayoutParams\nimport androidx.activity.SystemBarStyle\nimport androidx.activity.addCallback\nimport androidx.activity.enableEdgeToEdge\nimport androidx.activity.result.contract.ActivityResultContracts\nimport androidx.activity.viewModels\nimport androidx.core.util.size\nimport androidx.core.view.ViewCompat\nimport androidx.core.view.WindowInsetsCompat\nimport androidx.core.view.isGone\nimport androidx.core.view.updateLayoutParams\nimport androidx.core.view.updatePadding\nimport androidx.fragment.app.Fragment\nimport androidx.lifecycle.Lifecycle\nimport androidx.lifecycle.flowWithLifecycle\nimport androidx.lifecycle.lifecycleScope\nimport androidx.lifecycle.repeatOnLifecycle\nimport androidx.recyclerview.widget.GridLayoutManager\nimport androidx.recyclerview.widget.RecyclerView\nimport androidx.recyclerview.widget.RecyclerView.OnScrollListener\nimport com.yenaly.han1meviewer.ADVANCED_SEARCH_MAP\nimport com.yenaly.han1meviewer.AdvancedSearchMap\nimport com.yenaly.han1meviewer.HAdvancedSearch\nimport com.yenaly.han1meviewer.VideoCoverSize\nimport com.yenaly.han1meviewer.databinding.ActivitySearchBinding\nimport com.yenaly.han1meviewer.logic.entity.SearchHistoryEntity\nimport com.yenaly.han1meviewer.logic.model.HanimeInfo\nimport com.yenaly.han1meviewer.logic.model.SearchOption\nimport com.yenaly.han1meviewer.logic.model.SearchOption.Companion.flatten\nimport com.yenaly.han1meviewer.logic.model.SearchOption.Companion.get\nimport com.yenaly.han1meviewer.logic.state.PageLoadingState\nimport com.yenaly.han1meviewer.ui.StateLayoutMixin\nimport com.yenaly.han1meviewer.ui.adapter.FixedGridLayoutManager\nimport com.yenaly.han1meviewer.ui.adapter.HanimeSearchHistoryRvAdapter\nimport com.yenaly.han1meviewer.ui.adapter.HanimeVideoRvAdapter\nimport com.yenaly.han1meviewer.ui.fragment.search.HMultiChoicesDialog\nimport com.yenaly.han1meviewer.ui.fragment.search.SearchOptionsPopupFragment\nimport com.yenaly.han1meviewer.ui.viewmodel.MyListViewModel\nimport com.yenaly.han1meviewer.ui.viewmodel.SearchViewModel\nimport com.yenaly.han1meviewer.util.logScreenViewEvent\nimport com.yenaly.yenaly_libs.base.YenalyActivity\nimport com.yenaly.yenaly_libs.utils.dp\nimport com.yenaly.yenaly_libs.utils.intentExtra\nimport com.yenaly.yenaly_libs.utils.unsafeLazy\nimport kotlinx.coroutines.Dispatchers\nimport kotlinx.coroutines.ExperimentalCoroutinesApi\nimport kotlinx.coroutines.FlowPreview\nimport kotlinx.coroutines.flow.collectLatest\nimport kotlinx.coroutines.flow.debounce\nimport kotlinx.coroutines.flow.flatMapLatest\nimport kotlinx.coroutines.flow.flowOn\nimport kotlinx.coroutines.flow.launchIn\nimport kotlinx.coroutines.flow.onEach\nimport kotlinx.coroutines.launch\n\n/**\n * @project Hanime1\n * @author Yenaly Liew\n * @time 2022/06/13 013 22:29\n */\nclass SearchActivity : YenalyActivity<ActivitySearchBinding>(), StateLayoutMixin {\n\n    val viewModel by viewModels<SearchViewModel>()\n    val myListViewModel by viewModels<MyListViewModel>()\n\n    val subscribeLauncher =\n        registerForActivityResult(ActivityResultContracts.StartActivityForResult()) { result ->\n            if (result.resultCode == RESULT_OK) {\n                initSubscription()\n            }\n        }\n\n    /**\n     * 判断adapter是否已经加载，防止多次加载导致滑动浏览总是跳到顶部。\n     */\n    private var hasAdapterLoaded = false\n\n    private val searchAdapter by unsafeLazy { HanimeVideoRvAdapter() }\n    private val historyAdapter by unsafeLazy { HanimeSearchHistoryRvAdapter() }\n\n    private val advancedSearchMap by intentExtra<Any>(ADVANCED_SEARCH_MAP)\n\n    private val optionsPopupFragment by unsafeLazy { SearchOptionsPopupFragment() }\n\n    override fun getViewBinding(layoutInflater: LayoutInflater): ActivitySearchBinding =\n        ActivitySearchBinding.inflate(layoutInflater)\n\n    override val onFragmentResumedListener: (Fragment) -> Unit = { fragment ->\n        logScreenViewEvent(fragment)\n    }\n\n    override fun setUiStyle() {\n        enableEdgeToEdge(\n            statusBarStyle = SystemBarStyle.dark(Color.TRANSPARENT),\n            navigationBarStyle = SystemBarStyle.dark(Color.TRANSPARENT)\n        )\n    }\n\n    /**\n     * 初始化数据\n     */\n    override fun initData(savedInstanceState: Bundle?) {\n        advancedSearchMap?.let(::loadAdvancedSearch)\n\n        initSearchBar()\n        initSubscription()\n\n        binding.state.init()\n\n        binding.searchRv.apply {\n            layoutManager = FixedGridLayoutManager(\n                this@SearchActivity, VideoCoverSize.Normal.videoInOneLine\n            )\n            adapter = searchAdapter\n            clipToPadding = false\n            addOnScrollListener(object : OnScrollListener() {\n\n                override fun onScrollStateChanged(recyclerView: RecyclerView, newState: Int) {\n                    if (newState != RecyclerView.SCROLL_STATE_IDLE) {\n                        binding.searchBar.hideHistory()\n                    }\n                }\n            })\n        }\n        binding.searchSrl.apply {\n            setOnLoadMoreListener {\n                getHanimeSearchResult()\n            }\n            setOnRefreshListener {\n                // will enter here firstly. cuz the flow's def value is Loading.\n                getNewHanimeSearchResult()\n            }\n            setDisableContentWhenRefresh(true)\n        }\n\n        ViewCompat.setOnApplyWindowInsetsListener(binding.searchBar) { v, insets ->\n            v.updatePadding(top = insets.getInsets(WindowInsetsCompat.Type.systemBars()).top)\n            WindowInsetsCompat.CONSUMED\n        }\n        ViewCompat.setOnApplyWindowInsetsListener(binding.searchRv) { v, insets ->\n            val sysBars = insets.getInsets(WindowInsetsCompat.Type.systemBars())\n            v.updatePadding(bottom = sysBars.bottom, top = sysBars.top + 68.dp)\n            WindowInsetsCompat.CONSUMED\n        }\n        ViewCompat.setOnApplyWindowInsetsListener(binding.searchHeader) { v, insets ->\n            val sysBars = insets.getInsets(WindowInsetsCompat.Type.statusBars())\n            v.updateLayoutParams<MarginLayoutParams> {\n                topMargin = sysBars.top + 68.dp\n            }\n            WindowInsetsCompat.CONSUMED\n        }\n    }\n\n    @SuppressLint(\"SetTextI18n\")\n    override fun bindDataObservers() {\n        lifecycleScope.launch {\n            repeatOnLifecycle(Lifecycle.State.CREATED) {\n                viewModel.searchStateFlow.collect { state ->\n                    binding.searchRv.isGone = state is PageLoadingState.Error\n                    when (state) {\n                        is PageLoadingState.Loading -> {\n                            if (viewModel.searchFlow.value.isEmpty()) binding.searchSrl.autoRefresh()\n                        }\n\n                        is PageLoadingState.Success -> {\n                            viewModel.page++\n                            binding.searchSrl.finishRefresh()\n                            binding.searchSrl.finishLoadMore(true)\n                            if (!hasAdapterLoaded) {\n                                binding.searchRv.layoutManager =\n                                    state.info.buildFlexibleGridLayoutManager()\n                                hasAdapterLoaded = true\n                            }\n                            binding.state.showContent()\n                        }\n\n                        is PageLoadingState.NoMoreData -> {\n                            binding.searchSrl.finishLoadMoreWithNoMoreData()\n                            if (viewModel.searchFlow.value.isEmpty()) {\n                                binding.state.showEmpty()\n                                binding.searchRv.isGone = true\n                            }\n                        }\n\n                        is PageLoadingState.Error -> {\n                            binding.searchSrl.finishRefresh()\n                            binding.searchSrl.finishLoadMore(false)\n                            // set error view\n                            binding.state.showError(state.throwable)\n                        }\n                    }\n                }\n            }\n        }\n\n        lifecycleScope.launch {\n            repeatOnLifecycle(Lifecycle.State.CREATED) {\n                viewModel.searchFlow.collectLatest {\n                    searchAdapter.submitList(it)\n                }\n            }\n        }\n    }\n\n    override fun onConfigurationChanged(newConfig: Configuration) {\n        super.onConfigurationChanged(newConfig)\n        val dataState = viewModel.searchStateFlow.value\n        binding.searchRv.layoutManager = if (dataState is PageLoadingState.Success) {\n            dataState.info.buildFlexibleGridLayoutManager()\n        } else FixedGridLayoutManager(this, VideoCoverSize.Normal.videoInOneLine)\n    }\n\n    private fun getHanimeSearchResult() {\n        Log.d(\"SearchActivity\", buildString {\n            appendLine(\"page: ${viewModel.page}, query: ${viewModel.query}, genre: ${viewModel.genre}, \")\n            appendLine(\"sort: ${viewModel.sort}, broad: ${viewModel.broad}, year: ${viewModel.year}, \")\n            appendLine(\"month: ${viewModel.month}, duration: ${viewModel.duration}, \")\n            appendLine(\"tagMap: ${viewModel.tagMap}, brandMap: ${viewModel.brandMap}\")\n        })\n        viewModel.getHanimeSearchResult(\n            viewModel.page,\n            viewModel.query, viewModel.genre, viewModel.sort, viewModel.broad,\n            viewModel.year, viewModel.month, viewModel.duration,\n            viewModel.tagMap.flatten(), viewModel.brandMap.flatten()\n        )\n    }\n\n    /**\n     * 獲取最新結果，清除之前保存的所有數據\n     */\n    private fun getNewHanimeSearchResult() {\n        viewModel.page = 1\n        hasAdapterLoaded = false\n        viewModel.clearHanimeSearchResult()\n        getHanimeSearchResult()\n    }\n\n    @OptIn(FlowPreview::class, ExperimentalCoroutinesApi::class)\n    private fun initSearchBar() {\n        onBackPressedDispatcher.addCallback(this) {\n            if (binding.searchBar.hideHistory()) {\n                return@addCallback\n            }\n            finish()\n        }\n        historyAdapter.listener = object : HanimeSearchHistoryRvAdapter.OnItemViewClickListener {\n            override fun onItemClickListener(v: View, history: SearchHistoryEntity?) {\n                binding.searchBar.searchText = history?.query\n            }\n\n            override fun onItemRemoveListener(v: View, history: SearchHistoryEntity?) {\n                history?.let(viewModel::deleteSearchHistory)\n            }\n        }\n        binding.searchBar.apply hsb@{\n            historyAdapter = this@SearchActivity.historyAdapter\n            onTagClickListener = {\n                optionsPopupFragment.showIn(this@SearchActivity)\n            }\n            onBackClickListener = {\n                finish()\n            }\n            onSearchClickListener = { _, text ->\n                viewModel.query = text\n                if (text.isNotBlank()) {\n                    viewModel.insertSearchHistory(SearchHistoryEntity(text))\n                }\n                // getNewHanimeSearchResult() 下面那個方法自動幫你執行這個方法了\n                binding.searchSrl.autoRefresh()\n            }\n\n            // 搜索框文字改变走这里\n            textChangeFlow()\n                .debounce(300)\n                .flatMapLatest {\n                    viewModel.loadAllSearchHistories(it)\n                }.flowOn(Dispatchers.IO).onEach {\n                    this@SearchActivity.historyAdapter.submitList(it)\n                }.flowWithLifecycle(lifecycle).launchIn(lifecycleScope)\n        }\n\n        optionsPopupFragment.onSearchListener = {\n            optionsPopupFragment.dismiss()\n            binding.searchSrl.autoRefresh()\n        }\n    }\n\n    private fun initSubscription() {\n        myListViewModel.subscription.getSubscriptionsWithSinglePage()\n    }\n\n    private fun List<HanimeInfo>.buildFlexibleGridLayoutManager(): GridLayoutManager {\n        val counts = if (any { it.itemType == HanimeInfo.NORMAL })\n            VideoCoverSize.Normal.videoInOneLine else VideoCoverSize.Simplified.videoInOneLine\n        return FixedGridLayoutManager(this@SearchActivity, counts)\n    }\n\n    fun setSearchText(text: String?, canTextChange: Boolean = true) {\n        viewModel.query = text\n        binding.searchBar.searchText = text\n        binding.searchBar.canTextChange = canTextChange\n    }\n\n    val searchText: String? get() = binding.searchBar.searchText\n\n    /**\n     * 分析该 any 并加载给相应 ViewModel 中的参数\n     *\n     * any 可以爲 [AdvancedSearchMap] 或者 [String]\n     *\n     * 若为 [String]，则默认给 query 处理\n     */\n    @Suppress(\"UNCHECKED_CAST\")\n    private fun loadAdvancedSearch(any: Any) {\n        if (any is String) {\n            setSearchText(any)\n            return\n        }\n        val map = any as AdvancedSearchMap\n        (map[HAdvancedSearch.QUERY] as? String)?.let {\n            setSearchText(it)\n        }\n        viewModel.genre = map[HAdvancedSearch.GENRE] as? String\n        viewModel.sort = map[HAdvancedSearch.SORT] as? String\n        viewModel.year = map[HAdvancedSearch.YEAR] as? Int\n        viewModel.month = map[HAdvancedSearch.MONTH] as? Int\n        viewModel.duration = map[HAdvancedSearch.DURATION] as? String\n\n        when (val tags = map[HAdvancedSearch.TAGS]) {\n            is Map<*, *> -> {\n                val tagMap = tags as Map<Int, *>\n                tagMap.forEach { (k, v) ->\n                    val scope = viewModel.tags[k]\n                    when (v) {\n                        is String -> {\n                            val option = scope.find { it.searchKey == v }\n                            viewModel.tagMap[k] = option?.let(::setOf) ?: emptySet()\n                        }\n\n                        is Set<*> -> {\n                            val keySet = scope.filterTo(mutableSetOf()) { it.searchKey in v }\n                            viewModel.tagMap[k] = keySet\n                        }\n                    }\n                }\n            }\n\n            // 不推荐使用\n            is String -> {\n                kotlin.run t@{\n                    viewModel.tags.forEach { (k, v) ->\n                        v.find { it.searchKey == tags }?.let { so ->\n                            val scopeKey = SearchOption.toScopeKey(k)\n                            viewModel.tagMap[scopeKey] = setOf(so)\n                            return@t\n                        }\n                    }\n                }\n            }\n\n            // 不推荐使用\n            is Set<*> -> {\n                viewModel.tags.forEach { (k, v) ->\n                    val keySet = v.filterTo(mutableSetOf()) { it.searchKey in tags }\n                    viewModel.tagMap[SearchOption.toScopeKey(k)] = keySet\n                }\n            }\n        }\n        when (val brands = map[HAdvancedSearch.BRANDS]) {\n            is String -> {\n                val brand = viewModel.brands.find { it.searchKey == brands }\n                viewModel.brandMap[HMultiChoicesDialog.UNKNOWN_ADAPTER] =\n                    brand?.let(::setOf) ?: emptySet()\n            }\n\n            is Set<*> -> {\n                val keySet = viewModel.brands.filterTo(mutableSetOf()) { it.searchKey in brands }\n                viewModel.brandMap[HMultiChoicesDialog.UNKNOWN_ADAPTER] = keySet\n            }\n        }\n    }\n}"
  },
  {
    "path": "app/src/main/java/com/yenaly/han1meviewer/ui/activity/SettingsActivity.kt",
    "content": "package com.yenaly.han1meviewer.ui.activity\n\nimport android.graphics.Color\nimport android.os.Build\nimport android.os.Bundle\nimport android.view.LayoutInflater\nimport androidx.activity.SystemBarStyle\nimport androidx.activity.enableEdgeToEdge\nimport androidx.activity.viewModels\nimport androidx.core.view.ViewCompat\nimport androidx.core.view.WindowInsetsCompat\nimport androidx.core.view.updatePadding\nimport androidx.fragment.app.Fragment\nimport androidx.navigation.NavController\nimport androidx.navigation.fragment.NavHostFragment\nimport com.yenaly.han1meviewer.R\nimport com.yenaly.han1meviewer.databinding.ActivitySettingsBinding\nimport com.yenaly.han1meviewer.ui.viewmodel.SettingsViewModel\nimport com.yenaly.han1meviewer.util.logScreenViewEvent\nimport com.yenaly.yenaly_libs.base.YenalyActivity\n\n/**\n * @project Han1meViewer\n * @author Yenaly Liew\n * @time 2022/07/01 001 13:40\n */\nclass SettingsActivity : YenalyActivity<ActivitySettingsBinding>() {\n\n    val viewModel by viewModels<SettingsViewModel>()\n\n    private lateinit var navHostFragment: NavHostFragment\n    private lateinit var navController: NavController\n\n    val currentFragment get() = navHostFragment.childFragmentManager.primaryNavigationFragment\n\n    override fun getViewBinding(layoutInflater: LayoutInflater): ActivitySettingsBinding =\n        ActivitySettingsBinding.inflate(layoutInflater)\n\n    override val onFragmentResumedListener: (Fragment) -> Unit = { fragment ->\n        logScreenViewEvent(fragment)\n    }\n\n    override fun setUiStyle() {\n        enableEdgeToEdge(\n            statusBarStyle = SystemBarStyle.dark(Color.TRANSPARENT),\n            navigationBarStyle = SystemBarStyle.dark(Color.TRANSPARENT)\n        )\n    }\n\n    override fun initData(savedInstanceState: Bundle?) {\n        setSupportActionBar(binding.toolbar)\n        supportActionBar?.let {\n            it.setDisplayHomeAsUpEnabled(true)\n            it.setHomeActionContentDescription(R.string.back)\n        }\n        navHostFragment =\n            supportFragmentManager.findFragmentById(R.id.fcv_settings) as NavHostFragment\n        navController = navHostFragment.navController\n        SettingsRouter.with(navController).navigateFromActivity(inclusive = true)\n        binding.toolbar.setNavigationOnClickListener {\n            if (!navController.popBackStack()) {\n                onBackPressedDispatcher.onBackPressed()\n            }\n        }\n\n        if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.UPSIDE_DOWN_CAKE) {\n            overrideActivityTransition(\n                OVERRIDE_TRANSITION_OPEN,\n                R.anim.fade_in,\n                R.anim.fade_out\n            )\n            overrideActivityTransition(\n                OVERRIDE_TRANSITION_CLOSE,\n                R.anim.fade_in,\n                R.anim.fade_out\n            )\n        }\n\n        ViewCompat.setOnApplyWindowInsetsListener(binding.fcvSettings) { v, insets ->\n            val navBar = insets.getInsets(WindowInsetsCompat.Type.navigationBars())\n            v.updatePadding(bottom = navBar.bottom)\n            WindowInsetsCompat.CONSUMED\n        }\n    }\n\n    @Suppress(\"DEPRECATION\")\n    override fun finish() {\n        super.finish()\n        if (Build.VERSION.SDK_INT < Build.VERSION_CODES.UPSIDE_DOWN_CAKE) {\n            overridePendingTransition(R.anim.fade_in, R.anim.fade_out)\n        }\n    }\n}"
  },
  {
    "path": "app/src/main/java/com/yenaly/han1meviewer/ui/activity/SettingsRouter.kt",
    "content": "package com.yenaly.han1meviewer.ui.activity\n\nimport android.content.Context\nimport android.content.Intent\nimport android.os.Build\nimport android.os.Bundle\nimport androidx.annotation.IdRes\nimport androidx.fragment.app.Fragment\nimport androidx.navigation.NavController\nimport androidx.navigation.fragment.findNavController\nimport androidx.navigation.navOptions\nimport com.yenaly.han1meviewer.R\nimport com.yenaly.yenaly_libs.utils.activity\n\n/**\n * 极其简易的路由器，用于从任何地方跳转到设置界面\n */\nclass SettingsRouter private constructor(\n    private val context: Context,\n    private val navController: NavController\n) {\n    companion object {\n        const val DESTINATION = \"destination\"\n        const val BUNDLE = \"bundle\"\n\n        /**\n         * 适用于 fragment 内部跳转\n         */\n        @JvmStatic\n        fun with(fragment: Fragment) = SettingsRouter(\n            fragment.requireContext(),\n            fragment.findNavController()\n        )\n\n        /**\n         * 适用于 activity 跳转\n         */\n        @JvmStatic\n        fun with(navController: NavController) = SettingsRouter(\n            navController.context,\n            navController\n        )\n    }\n\n    /**\n     * 依靠 intent extra 运作，只适用于其他 activity 打开设置界面\n     */\n    fun navigateFromActivity(\n        args: Bundle? = null,\n        inclusive: Boolean = false\n    ) {\n        val activity = context.activity ?: return\n        val id = activity.intent.getIntExtra(DESTINATION, 0)\n        if (id == 0) return\n        navController.navigate(id, args, navOptions {\n            popUpTo(R.id.homeSettingsFragment) {\n                this.inclusive = true\n            }\n            anim {\n                enter = R.anim.fade_in\n                exit = R.anim.fade_out\n                popEnter = R.anim.fade_in\n                popExit = R.anim.fade_out\n            }\n        }.takeIf {\n            inclusive\n        })\n    }\n\n    fun toSettingsActivity(@IdRes id: Int = 0, bundle: Bundle? = null) {\n        val activity = context.activity ?: return\n        val intent = Intent(activity, SettingsActivity::class.java).apply {\n            putExtra(DESTINATION, id)\n            bundle?.let {\n                putExtra(BUNDLE, it)\n            }\n        }\n        activity.startActivity(intent)\n        if (Build.VERSION.SDK_INT < Build.VERSION_CODES.UPSIDE_DOWN_CAKE) {\n            @Suppress(\"DEPRECATION\")\n            activity.overridePendingTransition(R.anim.fade_in, R.anim.fade_out)\n        }\n    }\n\n    /**\n     * 适用于 Settings 内不同设置页面跳转\n     */\n    fun navigateWithinSettings(\n        @IdRes to: Int,\n        args: Bundle? = null,\n        inclusive: Boolean = false\n    ) {\n        val from = navController.currentDestination?.id ?: return\n        navController.navigate(to, args, navOptions {\n            popUpTo(from) {\n                this.inclusive = inclusive\n            }\n            anim {\n                enter = R.anim.fade_in\n                exit = R.anim.fade_out\n                popEnter = R.anim.fade_in\n                popExit = R.anim.fade_out\n            }\n        })\n    }\n}"
  },
  {
    "path": "app/src/main/java/com/yenaly/han1meviewer/ui/activity/VideoActivity.kt",
    "content": "package com.yenaly.han1meviewer.ui.activity\n\nimport android.app.PendingIntent\nimport android.app.PictureInPictureParams\nimport android.app.RemoteAction\nimport android.content.BroadcastReceiver\nimport android.content.Context\nimport android.content.Intent\nimport android.content.IntentFilter\nimport android.content.pm.ActivityInfo\nimport android.graphics.Color\nimport android.graphics.Rect\nimport android.graphics.drawable.Icon\nimport android.os.Build\nimport android.os.Bundle\nimport android.util.Log\nimport android.util.Rational\nimport android.view.Gravity\nimport android.view.LayoutInflater\nimport android.view.ViewGroup.LayoutParams.MATCH_PARENT\nimport android.view.ViewGroup.MarginLayoutParams\nimport android.widget.LinearLayout\nimport androidx.activity.SystemBarStyle\nimport androidx.activity.addCallback\nimport androidx.activity.enableEdgeToEdge\nimport androidx.activity.viewModels\nimport androidx.annotation.RequiresApi\nimport androidx.core.content.ContextCompat\nimport androidx.core.view.ViewCompat\nimport androidx.core.view.WindowInsetsCompat\nimport androidx.core.view.isVisible\nimport androidx.core.view.updateLayoutParams\nimport androidx.fragment.app.Fragment\nimport androidx.lifecycle.Lifecycle\nimport androidx.lifecycle.flowWithLifecycle\nimport androidx.lifecycle.lifecycleScope\nimport androidx.lifecycle.repeatOnLifecycle\nimport cn.jzvd.JZMediaInterface\nimport cn.jzvd.Jzvd\nimport cn.jzvd.Jzvd.goOnPlayOnPause\nimport coil.load\nimport com.google.firebase.Firebase\nimport com.google.firebase.analytics.FirebaseAnalytics\nimport com.google.firebase.analytics.analytics\nimport com.google.firebase.analytics.logEvent\nimport com.yenaly.han1meviewer.COMMENT_TYPE\nimport com.yenaly.han1meviewer.FROM_DOWNLOAD\nimport com.yenaly.han1meviewer.FirebaseConstants\nimport com.yenaly.han1meviewer.Preferences\nimport com.yenaly.han1meviewer.R\nimport com.yenaly.han1meviewer.VIDEO_CODE\nimport com.yenaly.han1meviewer.VIDEO_COMMENT_PREFIX\nimport com.yenaly.han1meviewer.databinding.ActivityVideoBinding\nimport com.yenaly.han1meviewer.getHanimeVideoLink\nimport com.yenaly.han1meviewer.logic.entity.HKeyframeEntity\nimport com.yenaly.han1meviewer.logic.entity.WatchHistoryEntity\nimport com.yenaly.han1meviewer.logic.exception.ParseException\nimport com.yenaly.han1meviewer.logic.state.VideoLoadingState\nimport com.yenaly.han1meviewer.ui.fragment.video.CommentFragment\nimport com.yenaly.han1meviewer.ui.fragment.video.VideoIntroductionFragment\nimport com.yenaly.han1meviewer.ui.view.video.HMediaKernel\nimport com.yenaly.han1meviewer.ui.view.video.HanimeDataSource\nimport com.yenaly.han1meviewer.ui.viewmodel.CommentViewModel\nimport com.yenaly.han1meviewer.ui.viewmodel.VideoViewModel\nimport com.yenaly.han1meviewer.util.getOrCreateBadgeOnTextViewAt\nimport com.yenaly.han1meviewer.util.logScreenViewEvent\nimport com.yenaly.han1meviewer.util.showAlertDialog\nimport com.yenaly.yenaly_libs.base.YenalyActivity\nimport com.yenaly.yenaly_libs.utils.OrientationManager\nimport com.yenaly.yenaly_libs.utils.activity\nimport com.yenaly.yenaly_libs.utils.browse\nimport com.yenaly.yenaly_libs.utils.dp\nimport com.yenaly.yenaly_libs.utils.intentExtra\nimport com.yenaly.yenaly_libs.utils.makeBundle\nimport com.yenaly.yenaly_libs.utils.showShortToast\nimport com.yenaly.yenaly_libs.utils.startActivity\nimport com.yenaly.yenaly_libs.utils.view.attach\nimport com.yenaly.yenaly_libs.utils.view.setUpFragmentStateAdapter\nimport kotlinx.coroutines.launch\nimport kotlinx.datetime.Clock\n\nclass VideoActivity : YenalyActivity<ActivityVideoBinding>(),\n    OrientationManager.OrientationChangeListener {\n    companion object {\n        private const val ACTION_PLAY = \"com.yenaly.han1meviewer.ACTION_PLAY\"\n        private const val ACTION_PAUSE = \"com.yenaly.han1meviewer.ACTION_PAUSE\"\n    }\n\n    val viewModel by viewModels<VideoViewModel>()\n    private val commentViewModel by viewModels<CommentViewModel>()\n\n    private val kernel = HMediaKernel.Type.fromString(Preferences.switchPlayerKernel)\n\n    private val fromDownload by intentExtra(FROM_DOWNLOAD, false)\n    private val videoCode by intentExtra<String>(VIDEO_CODE)\n    private var videoTitle: String? = null\n    private var videoCodeByWebsite: String? = null\n\n    private var builder: PictureInPictureParams.Builder? = null\n\n    private val tabNameArray = intArrayOf(R.string.introduction, R.string.comment)\n\n    override fun getViewBinding(layoutInflater: LayoutInflater): ActivityVideoBinding =\n        ActivityVideoBinding.inflate(layoutInflater)\n\n    override val onFragmentResumedListener: (Fragment) -> Unit = { fragment ->\n        logScreenViewEvent(fragment)\n    }\n\n    override fun setUiStyle() {\n        enableEdgeToEdge(\n            statusBarStyle = SystemBarStyle.dark(Color.TRANSPARENT),\n            navigationBarStyle = SystemBarStyle.dark(Color.TRANSPARENT)\n        )\n    }\n\n    override fun initData(savedInstanceState: Bundle?) {\n        if (intent.action == Intent.ACTION_VIEW) {\n            val uri = intent.data\n            uri?.let {\n                videoCodeByWebsite = it.getQueryParameter(\"v\")\n            }\n        }\n\n        onBackPressedDispatcher.addCallback(this) {\n            if (Jzvd.backPress()) {\n                return@addCallback\n            }\n            finish()\n        }\n\n        // 必须 fromDownload 在前，videoCode 在后\n        viewModel.fromDownload = fromDownload\n        requireNotNull(videoCodeByWebsite ?: videoCode).let {\n            viewModel.videoCode = it\n            commentViewModel.code = it\n            binding.videoPlayer.videoCode = it\n        }\n\n        ViewCompat.setOnApplyWindowInsetsListener(binding.videoPlayer) { v, insets ->\n            val navBar = insets.getInsets(WindowInsetsCompat.Type.statusBars())\n            v.updateLayoutParams<MarginLayoutParams> {\n                topMargin = navBar.top\n            }\n            WindowInsetsCompat.CONSUMED\n        }\n\n        lifecycle.addObserver(OrientationManager(this))\n        initViewPager()\n        initHKeyframe()\n        if (Preferences.isPipAllowed) initPIP()\n    }\n\n    override fun bindDataObservers() {\n        lifecycleScope.launch {\n            repeatOnLifecycle(Lifecycle.State.CREATED) {\n                viewModel.hanimeVideoStateFlow.collect { state ->\n                    when (state) {\n                        is VideoLoadingState.Error -> {\n                            showShortToast(state.throwable.localizedMessage)\n                            if (state.throwable is ParseException) {\n                                browse(getHanimeVideoLink(viewModel.videoCode))\n                            }\n                            finish()\n                        }\n\n                        is VideoLoadingState.Loading -> {\n\n                        }\n\n                        is VideoLoadingState.Success -> {\n                            videoTitle = state.info.title\n\n                            if (state.info.videoUrls.isEmpty()) {\n                                binding.videoPlayer.startButton.setOnClickListener {\n                                    showShortToast(R.string.fail_to_get_video_link)\n                                    browse(getHanimeVideoLink(viewModel.videoCode))\n                                }\n                            } else {\n                                binding.videoPlayer.setUp(\n                                    HanimeDataSource(state.info.title, state.info.videoUrls),\n                                    Jzvd.SCREEN_NORMAL, kernel\n                                )\n                            }\n                            binding.videoPlayer.posterImageView.load(state.info.coverUrl) {\n                                crossfade(true)\n                            }\n                            // 將觀看記錄保存數據庫\n                            if (!fromDownload) {\n                                val entity = WatchHistoryEntity(\n                                    state.info.coverUrl,\n                                    state.info.title,\n                                    state.info.uploadTimeMillis,\n                                    Clock.System.now().toEpochMilliseconds(),\n                                    viewModel.videoCode\n                                )\n                                viewModel.insertWatchHistoryWithCover(entity)\n                            }\n                        }\n\n                        is VideoLoadingState.NoContent -> {\n                            showShortToast(R.string.video_might_not_exist)\n                            finish()\n                        }\n                    }\n                }\n            }\n        }\n\n        lifecycleScope.launch {\n            viewModel.observeKeyframe(viewModel.videoCode)?.flowWithLifecycle(lifecycle)?.collect {\n                binding.videoPlayer.hKeyframe = it\n                viewModel.hKeyframes = it\n            }\n        }\n\n        lifecycleScope.launch {\n            viewModel.modifyHKeyframeFlow.collect { (_, reason) ->\n                showShortToast(reason)\n            }\n        }\n    }\n\n    override fun onStop() {\n        super.onStop()\n        goOnPlayOnPause()\n    }\n\n    override fun onDestroy() {\n        super.onDestroy()\n        Jzvd.releaseAllVideos()\n        unregisterReceiver()\n    }\n\n    override fun onOrientationChanged(orientation: OrientationManager.ScreenOrientation) {\n        if (Jzvd.CURRENT_JZVD != null\n            && (binding.videoPlayer.state == Jzvd.STATE_PLAYING || binding.videoPlayer.state == Jzvd.STATE_PAUSE)\n            && binding.videoPlayer.screen != Jzvd.SCREEN_TINY\n            && Jzvd.FULLSCREEN_ORIENTATION != ActivityInfo.SCREEN_ORIENTATION_PORTRAIT\n        ) {\n            if (orientation.isLandscape && binding.videoPlayer.screen == Jzvd.SCREEN_NORMAL) {\n                changeScreenFullLandscape(orientation)\n            } else if (orientation === OrientationManager.ScreenOrientation.PORTRAIT\n                && binding.videoPlayer.screen == Jzvd.SCREEN_FULLSCREEN\n            ) {\n                changeScreenNormal()\n            }\n        }\n    }\n\n    /**\n     * 竖屏并退出全屏\n     */\n    private fun changeScreenNormal() {\n        if (binding.videoPlayer.screen == Jzvd.SCREEN_FULLSCREEN) {\n            binding.videoPlayer.gotoNormalScreen()\n        }\n    }\n\n    /**\n     * 横屏\n     */\n    private fun changeScreenFullLandscape(orientation: OrientationManager.ScreenOrientation) {\n        //从竖屏状态进入横屏\n        if (binding.videoPlayer.screen != Jzvd.SCREEN_FULLSCREEN) {\n            if (System.currentTimeMillis() - Jzvd.lastAutoFullscreenTime > 2000) {\n                binding.videoPlayer.autoFullscreen(orientation)\n                Jzvd.lastAutoFullscreenTime = System.currentTimeMillis()\n            }\n        }\n    }\n\n    private fun initViewPager() {\n        binding.videoVp.offscreenPageLimit = 1\n\n        binding.videoVp.setUpFragmentStateAdapter(this) {\n            addFragment { VideoIntroductionFragment() }\n            if (!fromDownload) {\n                addFragment { CommentFragment().makeBundle(COMMENT_TYPE to VIDEO_COMMENT_PREFIX) }\n            }\n        }\n\n        binding.videoTl.attach(binding.videoVp) { tab, position ->\n            tab.setText(tabNameArray[position])\n        }\n    }\n\n    private fun initHKeyframe() {\n        binding.videoPlayer.onGoHomeClickListener = { _ ->\n            // singleTask 直接把所有 VideoActivity 都 finish 掉\n            startActivity<MainActivity>()\n        }\n        binding.videoPlayer.onKeyframeClickListener = { v ->\n            binding.videoPlayer.clickHKeyframe(v)\n        }\n        binding.videoPlayer.onKeyframeLongClickListener = {\n            val mi: JZMediaInterface? = binding.videoPlayer.mediaInterface\n            if (mi != null && !mi.isPlaying) {\n                val currentPosition = binding.videoPlayer.currentPositionWhenPlaying\n                it.context.showAlertDialog {\n                    setTitle(R.string.add_to_h_keyframe)\n                    setMessage(buildString {\n                        appendLine(getString(R.string.sure_to_add_to_h_keyframe))\n                        append(getString(R.string.current_position_d_ms, currentPosition))\n                    })\n                    setPositiveButton(R.string.confirm) { _, _ ->\n                        viewModel.appendHKeyframe(\n                            viewModel.videoCode,\n                            videoTitle ?: \"Untitled\",\n                            HKeyframeEntity.Keyframe(\n                                position = currentPosition,\n                                prompt = null // 這裏不要給太多負擔，保存就行了沒必要寫comment\n                            )\n                        )\n                        // 使用到这里说明用户可能是关键H帧目标用户\n                        Firebase.analytics.logEvent(FirebaseAnalytics.Event.SELECT_CONTENT) {\n                            param(\n                                FirebaseAnalytics.Param.ITEM_ID,\n                                FirebaseConstants.H_KEYFRAMES\n                            )\n                            param(\n                                FirebaseAnalytics.Param.CONTENT_TYPE,\n                                FirebaseConstants.H_KEYFRAMES\n                            )\n                        }\n                    }\n                    setNegativeButton(R.string.cancel, null)\n                }\n            } else {\n                showShortToast(R.string.pause_then_long_press)\n            }\n        }\n    }\n\n    fun updateVideoPlayerSize(isInPictureInPictureMode: Boolean) {\n        if (binding.videoPlayer.layoutParams !is LinearLayout.LayoutParams) return\n        if (isInPictureInPictureMode) {\n            // 进入画中画模式时，设置视频播放器的布局参数为全屏\n            // 避免画中画只显示部分画面.\n            val params = binding.videoPlayer.layoutParams as LinearLayout.LayoutParams\n            params.width = MATCH_PARENT\n            params.height = MATCH_PARENT\n            binding.videoPlayer.layoutParams = params\n        } else {\n            // 恢复原来的布局参数\n            val params = binding.videoPlayer.layoutParams as LinearLayout.LayoutParams\n            params.width = MATCH_PARENT\n            params.height = 250.dp\n            binding.videoPlayer.layoutParams = params\n        }\n    }\n\n    @Deprecated(\"Deprecated in android.app.Activity\")\n    override fun onPictureInPictureModeChanged(isInPictureInPictureMode: Boolean) {\n        super.onPictureInPictureModeChanged(isInPictureInPictureMode)\n        // 当全屏状态进入画中画时 videoPlayer.layoutParams 为 FrameLayout.LayoutParams\n        updateVideoPlayerSize(isInPictureInPictureMode)\n    }\n\n    private val playbackReceiver = object : BroadcastReceiver() {\n\n        override fun onReceive(context: Context, intent: Intent) {\n            when (intent.action) {\n                ACTION_PLAY -> binding.videoPlayer.startButton.performClick()\n                ACTION_PAUSE -> goOnPlayOnPause()\n            }\n            if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) {\n                updatePIPAction()\n            }\n\n            // 暂停时隐藏UI\n            binding.videoPlayer.topContainer.isVisible = false\n            binding.videoPlayer.bottomContainer.isVisible = false\n        }\n    }\n\n    private fun registerReceiver() {\n        val filter = IntentFilter().apply {\n            addAction(ACTION_PLAY)\n            addAction(ACTION_PAUSE)\n        }\n        activity?.let {\n            ContextCompat.registerReceiver(\n                it,\n                playbackReceiver,\n                filter,\n                ContextCompat.RECEIVER_EXPORTED\n            )\n        }\n    }\n    private fun unregisterReceiver() {\n        try {\n            unregisterReceiver(playbackReceiver)\n        } catch (e: IllegalArgumentException) {\n            Log.w(\"VideoActivity\", \"Receiver not registered: $e\")\n        }\n    }\n\n    @RequiresApi(Build.VERSION_CODES.O)\n    private fun updatePIPAction() {\n        val (iconRes, titleRes, actionRes) = if (binding.videoPlayer.state == Jzvd.STATE_PLAYING) {\n            Triple(R.drawable.pause_circle_24px, R.string.pause, ACTION_PAUSE)\n        } else {\n            Triple(R.drawable.play_circle_24px, R.string.continues, ACTION_PLAY)\n        }\n        val pauseIntent = PendingIntent.getBroadcast(\n            activity,\n            0,\n            Intent(actionRes).setPackage(activity?.packageName),\n            PendingIntent.FLAG_IMMUTABLE or PendingIntent.FLAG_UPDATE_CURRENT\n        )\n        val action = RemoteAction(\n            Icon.createWithResource(activity, iconRes),\n            getString(titleRes),\n            getString(titleRes),\n            pauseIntent\n        )\n        builder?.setActions(listOf(action))\n    }\n\n    private fun initPIP() {\n        registerReceiver()\n        if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) {\n            val aspectRatio = Rational(16, 9)\n            binding.videoPlayer.addOnLayoutChangeListener { _, _, _, _, _, _, _, _, _ ->\n                val sourceRectHint = Rect()\n                binding.videoPlayer.getGlobalVisibleRect(sourceRectHint)\n                builder = PictureInPictureParams.Builder()\n                    .setAspectRatio(aspectRatio)\n                    .setSourceRectHint(sourceRectHint)\n                updatePIPAction()\n                if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.S) {\n                    builder?.setAutoEnterEnabled(true)\n                }\n                setPictureInPictureParams(builder!!.build())\n            }\n        }\n    }\n\n    override fun onUserLeaveHint() {\n        super.onUserLeaveHint()\n        if (Preferences.isPipAllowed) {\n            enterPictureInPictureMode()\n        }\n    }\n\n    fun showRedDotCount(count: Int) {\n        binding.videoTl.getOrCreateBadgeOnTextViewAt(\n            tabNameArray.indexOf(R.string.comment),\n            null, Gravity.START, 2.dp\n        ) {\n            isVisible = count > 0\n            number = count\n        }\n    }\n}"
  },
  {
    "path": "app/src/main/java/com/yenaly/han1meviewer/ui/adapter/AdapterLikeDataBindingPage.kt",
    "content": "package com.yenaly.han1meviewer.ui.adapter\n\nimport androidx.databinding.ViewDataBinding\n\n/**\n * 利用 Adapter 性质的页面，同时运用 DataBinding\n */\ninterface AdapterLikeDataBindingPage<DB : ViewDataBinding> {\n    var binding: DB?\n}"
  },
  {
    "path": "app/src/main/java/com/yenaly/han1meviewer/ui/adapter/BaseSingleDifferAdapter.kt",
    "content": "package com.yenaly.han1meviewer.ui.adapter\n\nimport androidx.recyclerview.widget.AsyncDifferConfig\nimport androidx.recyclerview.widget.DiffUtil\nimport androidx.recyclerview.widget.RecyclerView\nimport com.chad.library.adapter4.BaseDifferAdapter\nimport kotlin.coroutines.resume\nimport kotlin.coroutines.suspendCoroutine\n\n/**\n * @project Han1meViewer\n * @author Yenaly Liew\n * @time 2024/04/05 005 20:44\n */\nabstract class BaseSingleDifferAdapter<T : Any, VH : RecyclerView.ViewHolder> :\n    BaseDifferAdapter<T, VH> {\n\n    private constructor(config: AsyncDifferConfig<T>, items: List<T>) : super(config, items)\n\n    constructor(diffCallback: DiffUtil.ItemCallback<T>) : this(\n        AsyncDifferConfig.Builder(diffCallback).build(), emptyList()\n    )\n\n    constructor(diffCallback: DiffUtil.ItemCallback<T>, item: T) : this(\n        AsyncDifferConfig.Builder(diffCallback).build(), listOf(item)\n    )\n\n    constructor(config: AsyncDifferConfig<T>) : this(config, emptyList())\n\n    constructor(config: AsyncDifferConfig<T>, item: T) : this(config, listOf(item))\n\n    var item: T?\n        get() = items.firstOrNull()\n        set(value) {\n            items = if (value != null) listOf(value) else emptyList()\n        }\n\n    suspend fun submit(item: T?) = suspendCoroutine { cont ->\n        val list = item?.let(::listOf).orEmpty()\n        submitList(list) {\n            cont.resume(Unit)\n        }\n    }\n\n    protected abstract fun onBindViewHolder(holder: VH, item: T?)\n\n    open fun onBindViewHolder(holder: VH, item: T?, payloads: List<Any>) {\n        onBindViewHolder(holder, item)\n    }\n\n    final override fun onBindViewHolder(holder: VH, position: Int, item: T?) {\n        onBindViewHolder(holder, item)\n    }\n\n    final override fun onBindViewHolder(holder: VH, position: Int, item: T?, payloads: List<Any>) {\n        onBindViewHolder(holder, item, payloads)\n    }\n\n    override fun add(data: T) {\n        throw RuntimeException(\"Please use setItem()\")\n    }\n\n    override fun add(position: Int, data: T) {\n        throw RuntimeException(\"Please use setItem()\")\n    }\n\n    override fun addAll(collection: Collection<T>) {\n        throw RuntimeException(\"Please use setItem()\")\n    }\n\n    override fun addAll(position: Int, collection: Collection<T>) {\n        throw RuntimeException(\"Please use setItem()\")\n    }\n\n    override fun remove(data: T) {\n        throw RuntimeException(\"Please use setItem()\")\n    }\n\n    override fun removeAtRange(range: IntRange) {\n        throw RuntimeException(\"Please use setItem()\")\n    }\n\n    override fun removeAt(position: Int) {\n        throw RuntimeException(\"Please use setItem()\")\n    }\n\n    override fun set(position: Int, data: T) {\n        throw RuntimeException(\"Please use setItem()\")\n    }\n}"
  },
  {
    "path": "app/src/main/java/com/yenaly/han1meviewer/ui/adapter/FixedGridLayoutManager.kt",
    "content": "package com.yenaly.han1meviewer.ui.adapter\n\nimport android.content.Context\nimport androidx.recyclerview.widget.GridLayoutManager\n\n/**\n * @project Han1meViewer\n * @author Yenaly Liew\n * @time 2022/09/10 010 00:24\n */\nclass FixedGridLayoutManager(\n    context: Context?,\n    spanCount: Int\n) : GridLayoutManager(context, spanCount) {\n    override fun supportsPredictiveItemAnimations(): Boolean {\n        return false\n    }\n}"
  },
  {
    "path": "app/src/main/java/com/yenaly/han1meviewer/ui/adapter/HKeyframesRvAdapter.kt",
    "content": "package com.yenaly.han1meviewer.ui.adapter\n\nimport android.content.Context\nimport android.util.Base64\nimport android.view.View\nimport android.view.ViewGroup\nimport android.widget.ImageButton\nimport android.widget.TextView\nimport androidx.core.text.method.LinkMovementMethodCompat\nimport androidx.recyclerview.widget.DiffUtil\nimport androidx.recyclerview.widget.LinearLayoutManager\nimport androidx.recyclerview.widget.RecyclerView\nimport cn.jzvd.JZUtils\nimport com.chad.library.adapter4.BaseDifferAdapter\nimport com.chad.library.adapter4.viewholder.QuickViewHolder\nimport com.google.android.material.button.MaterialButton\nimport com.itxca.spannablex.spannable\nimport com.lxj.xpopup.XPopup\nimport com.yenaly.han1meviewer.R\nimport com.yenaly.han1meviewer.VIDEO_CODE\nimport com.yenaly.han1meviewer.logic.entity.HKeyframeEntity\nimport com.yenaly.han1meviewer.ui.activity.SettingsActivity\nimport com.yenaly.han1meviewer.ui.activity.VideoActivity\nimport com.yenaly.han1meviewer.util.showAlertDialog\nimport com.yenaly.yenaly_libs.utils.activity\nimport com.yenaly.yenaly_libs.utils.copyToClipboard\nimport com.yenaly.yenaly_libs.utils.encodeToStringByBase64\nimport com.yenaly.yenaly_libs.utils.showShortToast\nimport com.yenaly.yenaly_libs.utils.startActivity\nimport kotlinx.serialization.encodeToString\nimport kotlinx.serialization.json.Json\n\n/**\n * @project Han1meViewer\n * @author Yenaly Liew\n * @time 2023/11/26 026 17:42\n */\nclass HKeyframesRvAdapter : BaseDifferAdapter<HKeyframeEntity, QuickViewHolder>(COMPARATOR) {\n\n    init {\n        isStateViewEnable = true\n    }\n\n    companion object {\n        val COMPARATOR = object : DiffUtil.ItemCallback<HKeyframeEntity>() {\n            override fun areItemsTheSame(\n                oldItem: HKeyframeEntity,\n                newItem: HKeyframeEntity,\n            ) = oldItem.videoCode == newItem.videoCode\n\n            override fun areContentsTheSame(\n                oldItem: HKeyframeEntity,\n                newItem: HKeyframeEntity,\n            ) = oldItem == newItem\n        }\n\n        private val editResArray = intArrayOf(R.string.edit, R.string.delete, R.string.share)\n        private val editResIconArray = intArrayOf(\n            R.drawable.baseline_edit_24,\n            R.drawable.ic_baseline_delete_24,\n            R.drawable.ic_baseline_share_24\n        )\n    }\n\n    override fun onBindViewHolder(holder: QuickViewHolder, position: Int, item: HKeyframeEntity?) {\n        item ?: return\n        holder.setText(R.id.tv_title, item.title)\n        holder.setGone(R.id.btn_edit, item.author != null)\n        holder.getView<TextView>(R.id.tv_video_code).apply {\n            movementMethod = LinkMovementMethodCompat.getInstance()\n            text = spannable {\n                context.getString(R.string.h_keyframe_title_prefix).text()\n                item.videoCode.span {\n                    clickable(color = context.getColor(R.color.video_code_link_text_color)) { _, videoCode ->\n                        context.activity?.startActivity<VideoActivity>(VIDEO_CODE to videoCode)\n                    }\n                    underline()\n                }\n            }\n        }\n        holder.getView<RecyclerView>(R.id.rv_h_keyframe).apply {\n            layoutManager = LinearLayoutManager(context)\n            adapter = HKeyframeRvAdapter(item.videoCode, item).apply {\n                isLocal = item.author == null\n            }\n        }\n    }\n\n    override fun onCreateViewHolder(\n        context: Context,\n        parent: ViewGroup,\n        viewType: Int,\n    ): QuickViewHolder {\n        return QuickViewHolder(R.layout.item_h_keyframes, parent).also { viewHolder ->\n            viewHolder.getView<ImageButton>(R.id.btn_edit).apply {\n                setOnClickListener { view ->\n                    val position = viewHolder.bindingAdapterPosition\n                    val item = getItem(position) ?: return@setOnClickListener\n                    XPopup.Builder(view.context).atView(view).isDarkTheme(true).asAttachList(\n                        editResArray.map(view.context::getString).toTypedArray(),\n                        editResIconArray\n                    ) { pos, _ ->\n                        when (pos) {\n                            0 -> modify(item) // 修改\n\n                            1 -> delete(item) // 刪除\n\n                            2 -> share(item)  // 分享\n                        }\n                    }.show()\n                }\n            }\n        }\n    }\n\n    private fun share(item: HKeyframeEntity) {\n        val toJson = Json.encodeToString(item)\n        val toBase64 = toJson.encodeToStringByBase64(flag = Base64.NO_WRAP)\n        val toContent = buildString {\n            append(\">>>\")\n            append(toBase64)\n            append(\"<<<\")\n        }\n        context.showAlertDialog {\n            setTitle(R.string.share_to_others)\n            setMessage(context.getString(R.string.share_to_others_tip, toContent))\n            setPositiveButton(R.string.copy_) { _, _ ->\n                toContent.copyToClipboard()\n                showShortToast(R.string.copy_to_clipboard)\n            }\n            setNegativeButton(R.string.cancel, null)\n        }\n    }\n\n    private fun delete(item: HKeyframeEntity) {\n        val activity = context\n        if (activity is SettingsActivity) {\n            activity.showAlertDialog {\n                setTitle(R.string.sure_to_delete)\n                setMessage(item.title)\n                setPositiveButton(R.string.confirm) { _, _ ->\n                    activity.viewModel.deleteHKeyframes(item)\n                }\n                setNegativeButton(R.string.cancel, null)\n            }\n        }\n    }\n\n    private fun modify(item: HKeyframeEntity) {\n        val activity = context\n        if (activity is SettingsActivity) {\n            val view = View.inflate(activity, R.layout.dialog_modify_h_keyframes, null)\n            val etTitle = view.findViewById<TextView>(R.id.et_title)\n            val etVideoCode = view.findViewById<TextView>(R.id.et_video_code)\n            etTitle.text = item.title\n            etVideoCode.text = item.videoCode\n            activity.showAlertDialog {\n                setTitle(R.string.modify_h_keyframe)\n                setView(view)\n                setPositiveButton(R.string.confirm) { _, _ ->\n                    val title = etTitle.text.toString()\n                    activity.viewModel.updateHKeyframes(item.copy(title = title))\n                }\n                setNegativeButton(R.string.cancel, null)\n            }\n        }\n    }\n}\n\n/**\n * @project Han1meViewer\n * @author Yenaly Liew\n * @time 2023/11/26 026 17:42\n */\nclass HKeyframeRvAdapter(\n    private val videoCode: String,\n    keyframe: HKeyframeEntity? = null,\n) : BaseDifferAdapter<HKeyframeEntity.Keyframe, QuickViewHolder>(\n    COMPARATOR, keyframe?.keyframes.orEmpty()\n) {\n\n    init {\n        isStateViewEnable = true\n    }\n\n    /**\n     * 是否是本地关键帧\n     *\n     * @return false if is shared, true otherwise.\n     */\n    var isLocal: Boolean = true\n\n    var isShared: Boolean = false\n\n    companion object {\n        val COMPARATOR = object : DiffUtil.ItemCallback<HKeyframeEntity.Keyframe>() {\n            override fun areItemsTheSame(\n                oldItem: HKeyframeEntity.Keyframe,\n                newItem: HKeyframeEntity.Keyframe,\n            ) = oldItem.position == newItem.position\n\n            override fun areContentsTheSame(\n                oldItem: HKeyframeEntity.Keyframe,\n                newItem: HKeyframeEntity.Keyframe,\n            ) = oldItem == newItem\n        }\n    }\n\n    override fun onBindViewHolder(\n        holder: QuickViewHolder,\n        position: Int,\n        item: HKeyframeEntity.Keyframe?,\n    ) {\n        item ?: return\n        holder.setText(R.id.tv_keyframe, JZUtils.stringForTime(item.position))\n        holder.setText(R.id.tv_index, \"#${holder.bindingAdapterPosition + 1}\")\n\n        holder.setGone(R.id.btn_delete, !isLocal)\n        holder.setGone(R.id.btn_edit, !isLocal)\n\n        if (!item.prompt.isNullOrBlank()) {\n            holder.setGone(R.id.tv_prompt, false)\n            holder.setText(R.id.tv_prompt, \"➥ \" + item.prompt)\n        } else {\n            holder.setGone(R.id.tv_prompt, true)\n        }\n    }\n\n    override fun onCreateViewHolder(\n        context: Context,\n        parent: ViewGroup,\n        viewType: Int,\n    ): QuickViewHolder {\n        return QuickViewHolder(R.layout.item_h_keyframe, parent).also { viewHolder ->\n            if (isShared) return@also\n            viewHolder.getView<MaterialButton>(R.id.btn_edit).apply {\n                setOnClickListener {\n                    val position = viewHolder.bindingAdapterPosition\n                    val item = getItem(position) ?: return@setOnClickListener\n\n                    val view = View.inflate(context, R.layout.dialog_modify_h_keyframe, null)\n                    val etPrompt = view.findViewById<TextView>(R.id.et_prompt)\n                    val etPosition = view.findViewById<TextView>(R.id.et_position)\n                    etPrompt.text = item.prompt\n                    etPosition.text = item.position.toString()\n\n                    context.showAlertDialog {\n                        setTitle(R.string.modify_h_keyframe)\n                        setView(view)\n                        setPositiveButton(R.string.confirm) { _, _ ->\n                            val prompt = etPrompt.text.toString()\n                            val pos = etPosition.text.toString().toLong()\n                            when (context) {\n                                is SettingsActivity -> {\n                                    context.viewModel.modifyHKeyframe(\n                                        videoCode, item, HKeyframeEntity.Keyframe(\n                                            position = pos,\n                                            prompt = prompt\n                                        )\n                                    )\n                                    showShortToast(R.string.modify_success)\n                                }\n\n                                is VideoActivity -> {\n                                    context.viewModel.modifyHKeyframe(\n                                        videoCode, item, HKeyframeEntity.Keyframe(\n                                            position = pos,\n                                            prompt = prompt\n                                        )\n                                    )\n                                    // showShortToast(\"修改成功\") // 這裏不用提示，因為 VideoActivity 有 Flow 操控\n                                }\n                            }\n                        }\n                        setNegativeButton(R.string.cancel, null)\n                    }\n                }\n            }\n            viewHolder.getView<MaterialButton>(R.id.btn_delete).apply {\n                setOnClickListener {\n                    val position = viewHolder.bindingAdapterPosition\n                    val item = getItem(position) ?: return@setOnClickListener\n                    it.context.showAlertDialog {\n                        setTitle(R.string.sure_to_delete)\n                        setMessage(JZUtils.stringForTime(item.position))\n                        setPositiveButton(R.string.confirm) { _, _ ->\n                            when (context) {\n                                is SettingsActivity -> {\n                                    context.viewModel.removeHKeyframe(videoCode, item)\n                                    showShortToast(R.string.delete_success)\n                                }\n\n                                is VideoActivity -> {\n                                    context.viewModel.removeHKeyframe(videoCode, item)\n                                    // showShortToast(\"刪除成功\") // 這裏不用提示，因為 VideoActivity 有 Flow 操控\n                                }\n                            }\n                        }\n                        setNegativeButton(R.string.cancel, null)\n                    }\n                }\n            }\n        }\n    }\n}"
  },
  {
    "path": "app/src/main/java/com/yenaly/han1meviewer/ui/adapter/HRvItemAdapter.kt",
    "content": "package com.yenaly.han1meviewer.ui.adapter\n\nimport android.view.View\nimport android.view.ViewGroup\nimport android.widget.LinearLayout\nimport androidx.annotation.StringRes\nimport androidx.recyclerview.widget.LinearLayoutManager\nimport androidx.recyclerview.widget.RecyclerView\nimport androidx.recyclerview.widget.RecyclerView.ViewHolder\nimport com.yenaly.han1meviewer.ui.adapter.RvWrapper.Companion.wrappedWith\n\nclass HRvItemAdapter(\n    private val titleAdapter: VideoColumnTitleAdapter,\n    private val contentAdapter: RvWrapper<out ViewHolder>,\n): RecyclerView.Adapter<ViewHolder>() {\n    class HLinearLayoutViewHolder(val linearLayout: LinearLayout) : ViewHolder(linearLayout)\n\n    companion object {\n        fun RecyclerView.Adapter<*>.withTitleSection(\n            @StringRes titleResId: Int,\n            onMoreListener: (View) -> Unit\n        ): HRvItemAdapter {\n            val titleAdapter = VideoColumnTitleAdapter(titleResId).apply {\n                onMoreHanimeListener = onMoreListener\n            }\n            val contentAdapter = this.wrappedWith {\n                LinearLayoutManager(null, LinearLayoutManager.HORIZONTAL, false)\n            }\n            return HRvItemAdapter(titleAdapter, contentAdapter)\n        }\n    }\n\n    override fun onCreateViewHolder(\n        parent: ViewGroup,\n        viewType: Int\n    ): ViewHolder {\n        val linearLayout = LinearLayout(parent.context).apply {\n            layoutParams = ViewGroup.LayoutParams(\n                ViewGroup.LayoutParams.MATCH_PARENT,\n                ViewGroup.LayoutParams.WRAP_CONTENT\n            )\n            orientation = LinearLayout.VERTICAL\n            isNestedScrollingEnabled = false\n        }\n\n        return HLinearLayoutViewHolder(linearLayout)\n    }\n\n    override fun onBindViewHolder(\n        holder: ViewHolder,\n        position: Int\n    ) {\n        val linearLayout = (holder as HLinearLayoutViewHolder).linearLayout\n        linearLayout.removeAllViews()\n\n        val titleRecyclerView = RecyclerView(linearLayout.context).apply {\n            layoutParams = LinearLayout.LayoutParams(\n                LinearLayout.LayoutParams.MATCH_PARENT,\n                LinearLayout.LayoutParams.WRAP_CONTENT\n            )\n            adapter = titleAdapter\n            layoutManager = LinearLayoutManager(context)\n            isNestedScrollingEnabled = false\n        }\n        linearLayout.addView(titleRecyclerView)\n\n        val contentRecyclerView = RecyclerView(linearLayout.context).apply {\n            layoutParams = LinearLayout.LayoutParams(\n                LinearLayout.LayoutParams.MATCH_PARENT,\n                LinearLayout.LayoutParams.WRAP_CONTENT\n            )\n            adapter = contentAdapter\n            layoutManager = LinearLayoutManager(context)\n            isNestedScrollingEnabled = false\n        }\n        linearLayout.addView(contentRecyclerView)\n    }\n\n    override fun getItemCount(): Int = 1\n}"
  },
  {
    "path": "app/src/main/java/com/yenaly/han1meviewer/ui/adapter/HSearchTagAdapter.kt",
    "content": "package com.yenaly.han1meviewer.ui.adapter\n\nimport android.content.Context\nimport android.text.TextUtils\nimport android.view.ViewGroup\nimport com.chad.library.adapter4.BaseQuickAdapter\nimport com.chad.library.adapter4.viewholder.QuickViewHolder\nimport com.google.android.material.checkbox.MaterialCheckBox\nimport com.yenaly.han1meviewer.logic.model.SearchOption\n\nclass HSearchTagAdapter : BaseQuickAdapter<SearchOption, QuickViewHolder>() {\n\n    val checkedSet = mutableSetOf<SearchOption>()\n\n    override fun onBindViewHolder(holder: QuickViewHolder, position: Int, item: SearchOption?) {\n        item ?: return\n        val chip = holder.itemView as MaterialCheckBox\n        chip.isChecked = item in checkedSet\n        chip.text = item.value\n    }\n\n    override fun onCreateViewHolder(\n        context: Context,\n        parent: ViewGroup,\n        viewType: Int\n    ): QuickViewHolder {\n        val checkBox = MaterialCheckBox(context).apply {\n            maxLines = 2\n            ellipsize = TextUtils.TruncateAt.END\n        }\n        return QuickViewHolder(checkBox).apply {\n            checkBox.setOnCheckedChangeListener { _, isChecked ->\n                val position = bindingAdapterPosition\n                getItem(position)?.let { item ->\n                    if (isChecked) {\n                        checkedSet += item\n                    } else {\n                        checkedSet -= item\n                    }\n                }\n            }\n        }\n    }\n}"
  },
  {
    "path": "app/src/main/java/com/yenaly/han1meviewer/ui/adapter/HSubscriptionAdapter.kt",
    "content": "package com.yenaly.han1meviewer.ui.adapter\n\nimport android.content.Context\nimport android.view.View\nimport android.view.ViewGroup\nimport android.widget.CheckBox\nimport android.widget.ImageView\nimport androidx.core.view.isGone\nimport androidx.core.view.isVisible\nimport androidx.recyclerview.widget.DiffUtil\nimport coil.load\nimport coil.transform.CircleCropTransformation\nimport com.chad.library.adapter4.BaseDifferAdapter\nimport com.chad.library.adapter4.viewholder.QuickViewHolder\nimport com.yenaly.han1meviewer.R\nimport com.yenaly.han1meviewer.logic.model.Subscription\nimport com.yenaly.han1meviewer.ui.activity.SearchActivity\nimport com.yenaly.han1meviewer.util.showAlertDialog\n\nclass HSubscriptionAdapter : BaseDifferAdapter<Subscription, QuickViewHolder>(COMPARATOR) {\n\n    companion object {\n        const val DELETE = 1\n        const val CHECK = 1 shl 1\n    }\n\n    private object COMPARATOR : DiffUtil.ItemCallback<Subscription>() {\n        override fun areItemsTheSame(oldItem: Subscription, newItem: Subscription): Boolean {\n            return oldItem.name == newItem.name\n        }\n\n        override fun areContentsTheSame(oldItem: Subscription, newItem: Subscription): Boolean {\n            return oldItem == newItem\n        }\n\n        override fun getChangePayload(oldItem: Subscription, newItem: Subscription): Int {\n            var bitmap = 0\n            if (oldItem.isDeleteVisible != newItem.isDeleteVisible) {\n                bitmap = bitmap or DELETE\n            }\n            return bitmap\n        }\n    }\n\n    override fun onBindViewHolder(holder: QuickViewHolder, position: Int, item: Subscription?) {\n        item ?: return\n        val context = this.context\n        if (context is SearchActivity) {\n            holder.getView<CheckBox>(R.id.cb_select).apply {\n                isVisible = item.name == context.viewModel.subscriptionBrand\n                isChecked = isVisible\n            }\n            holder.getView<View>(R.id.btn_delete).isVisible = item.isDeleteVisible\n        }\n        holder.setText(R.id.tv_artist, item.name)\n        holder.getView<ImageView>(R.id.iv_artist).apply {\n            load(item.avatarUrl) {\n                crossfade(true)\n                transformations(CircleCropTransformation())\n            }\n        }\n    }\n\n    override fun onBindViewHolder(\n        holder: QuickViewHolder,\n        position: Int,\n        item: Subscription?,\n        payloads: List<Any>\n    ) {\n        item ?: return\n        if (payloads.isEmpty()) {\n            onBindViewHolder(holder, position, item)\n            return\n        }\n        val payload = payloads.first() as Int\n        if (payload and DELETE != 0) {\n            holder.getView<View>(R.id.btn_delete).isVisible = item.isDeleteVisible\n            holder.getView<View>(R.id.cb_select).isVisible = item.isCheckBoxVisible\n        }\n        if (payload and CHECK != 0) {\n            val context = this.context\n            if (context is SearchActivity) {\n                holder.getView<CheckBox>(R.id.cb_select).apply {\n                    isVisible = item.name == context.viewModel.subscriptionBrand\n                    isChecked = isVisible\n                }\n            }\n        }\n    }\n\n    override fun onCreateViewHolder(\n        context: Context,\n        parent: ViewGroup,\n        viewType: Int\n    ): QuickViewHolder {\n        return QuickViewHolder(R.layout.item_h_subscription, parent).apply {\n            itemView.setOnClickListener {\n                val position = bindingAdapterPosition\n                val item = getItem(position) ?: return@setOnClickListener\n                if (item.isDeleteVisible) return@setOnClickListener\n                getView<CheckBox>(R.id.cb_select).apply {\n                    if (isChecked) {\n                        isVisible = false\n                        isChecked = false\n                        if (context is SearchActivity) {\n                            context.viewModel.subscriptionBrand = null\n                            context.setSearchText(null)\n                            notifyItemChanged(position, CHECK)\n                        }\n                    } else {\n                        isVisible = true\n                        isChecked = true\n                        if (context is SearchActivity) {\n                            context.viewModel.subscriptionBrand = item.name\n                            context.setSearchText(item.name, canTextChange = false)\n                            notifyItemRangeChanged(0, itemCount, CHECK)\n                        }\n                    }\n                }\n            }\n            itemView.setOnLongClickListener {\n                if (context is SearchActivity) {\n                    val btnDelete = getView<View>(R.id.btn_delete)\n                    if (btnDelete.isGone) {\n                        submitList(items.map {\n                            it.copy(\n                                isDeleteVisible = true,\n                                isCheckBoxVisible = false\n                            )\n                        })\n                    } else {\n                        submitList(items.map {\n                            it.copy(\n                                isDeleteVisible = false,\n                                isCheckBoxVisible = it.name == context.viewModel.subscriptionBrand\n                            )\n                        })\n                    }\n                }\n                true\n            }\n            getView<View>(R.id.btn_delete).setOnClickListener {\n                val position = bindingAdapterPosition\n                val item = getItem(position) ?: return@setOnClickListener\n                context.showAlertDialog {\n                    setTitle(R.string.sure_to_delete)\n                    setMessage(context.getString(R.string.sure_to_delete_s, item.name))\n                    setPositiveButton(R.string.confirm) { _, _ ->\n                        if (context is SearchActivity) {\n                            context.myListViewModel.subscription.deleteSubscription(\n                                item.artistId, position\n                            )\n                        }\n                    }\n                    setNegativeButton(R.string.cancel, null)\n                }\n            }\n        }\n    }\n}"
  },
  {
    "path": "app/src/main/java/com/yenaly/han1meviewer/ui/adapter/HanimeDownloadedRvAdapter.kt",
    "content": "package com.yenaly.han1meviewer.ui.adapter\n\nimport android.content.Context\nimport android.graphics.RenderEffect\nimport android.graphics.Shader\nimport android.os.Build\nimport android.view.LayoutInflater\nimport android.view.ViewGroup\nimport androidx.core.net.toFile\nimport androidx.core.net.toUri\nimport androidx.core.view.updateLayoutParams\nimport androidx.recyclerview.widget.DiffUtil\nimport com.chad.library.adapter4.BaseDifferAdapter\nimport com.chad.library.adapter4.viewholder.DataBindingHolder\nimport com.yenaly.han1meviewer.HFileManager\nimport com.yenaly.han1meviewer.LOCAL_DATE_TIME_FORMAT\nimport com.yenaly.han1meviewer.R\nimport com.yenaly.han1meviewer.VIDEO_CODE\nimport com.yenaly.han1meviewer.databinding.ItemHanimeDownloadedBinding\nimport com.yenaly.han1meviewer.logic.entity.download.VideoWithCategories\nimport com.yenaly.han1meviewer.ui.activity.VideoActivity\nimport com.yenaly.han1meviewer.ui.fragment.home.download.DownloadedFragment\nimport com.yenaly.han1meviewer.util.HImageMeower.loadUnhappily\nimport com.yenaly.han1meviewer.util.MediaUtils\nimport com.yenaly.han1meviewer.util.openDownloadedHanimeVideoInActivity\nimport com.yenaly.han1meviewer.util.showAlertDialog\nimport com.yenaly.yenaly_libs.utils.activity\nimport com.yenaly.yenaly_libs.utils.dpF\nimport com.yenaly.yenaly_libs.utils.formatFileSizeV2\nimport com.yenaly.yenaly_libs.utils.startActivity\nimport kotlinx.datetime.Instant\nimport kotlinx.datetime.TimeZone\nimport kotlinx.datetime.format\nimport kotlinx.datetime.toLocalDateTime\n\n/**\n * @project Han1meViewer\n * @author Yenaly Liew\n * @time 2023/11/26 026 16:57\n */\nclass HanimeDownloadedRvAdapter(private val fragment: DownloadedFragment) :\n    BaseDifferAdapter<VideoWithCategories, DataBindingHolder<ItemHanimeDownloadedBinding>>(\n        COMPARATOR\n    ) {\n\n    init {\n        isStateViewEnable = true\n    }\n\n    companion object {\n        val COMPARATOR = object : DiffUtil.ItemCallback<VideoWithCategories>() {\n            override fun areContentsTheSame(\n                oldItem: VideoWithCategories,\n                newItem: VideoWithCategories,\n            ): Boolean {\n                return oldItem == newItem\n            }\n\n            override fun areItemsTheSame(\n                oldItem: VideoWithCategories,\n                newItem: VideoWithCategories,\n            ): Boolean {\n                return oldItem.video.id == newItem.video.id\n            }\n        }\n    }\n\n    override fun onBindViewHolder(\n        holder: DataBindingHolder<ItemHanimeDownloadedBinding>,\n        position: Int,\n        item: VideoWithCategories?,\n    ) {\n        item ?: return\n        holder.binding.tvTitle.text = item.video.title\n        holder.binding.tvVideoCode.text = item.video.videoCode\n        holder.binding.ivCover.loadUnhappily(item.video.coverUri, item.video.coverUrl)\n        holder.itemView.post {\n            // fast path\n            if (holder.itemView.height == holder.binding.vCoverBg.height) return@post\n            holder.binding.vCoverBg.updateLayoutParams {\n                height = holder.itemView.height\n            }\n            holder.binding.ivCoverBg.updateLayoutParams {\n                height = holder.itemView.height\n            }\n        }\n        holder.binding.ivCoverBg.apply {\n            loadUnhappily(item.video.coverUri, item.video.coverUrl)\n            if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.S) {\n                setRenderEffect(\n                    RenderEffect.createBlurEffect(\n                        8.dpF, 8.dpF,\n                        Shader.TileMode.CLAMP\n                    )\n                )\n            }\n        }\n        holder.binding.tvAddedTime.text =\n            Instant.fromEpochMilliseconds(item.video.addDate).toLocalDateTime(\n                TimeZone.currentSystemDefault()\n            ).format(LOCAL_DATE_TIME_FORMAT)\n\n        val realSize = item.video.videoUri.toUri().toFile().length()\n        holder.binding.tvSize.text = if (realSize == 0L) {\n            \"???\"\n        } else {\n            item.video.length.formatFileSizeV2()\n        }\n        holder.binding.tvQuality.text = item.video.quality\n    }\n\n    override fun onCreateViewHolder(\n        context: Context,\n        parent: ViewGroup,\n        viewType: Int,\n    ): DataBindingHolder<ItemHanimeDownloadedBinding> {\n        return DataBindingHolder(\n            ItemHanimeDownloadedBinding.inflate(\n                LayoutInflater.from(context), parent, false\n            )\n        ).also { viewHolder ->\n            viewHolder.itemView.setOnClickListener {\n                val position = viewHolder.bindingAdapterPosition\n                val item = getItem(position) ?: return@setOnClickListener\n                context.activity?.startActivity<VideoActivity>(VIDEO_CODE to item.video.videoCode)\n            }\n            viewHolder.binding.btnDelete.setOnClickListener {\n                val position = viewHolder.bindingAdapterPosition\n                // #issue-158: 这里可能为空\n                val item = getItem(position)\n                item?.let {\n                    context.showAlertDialog {\n                        setTitle(R.string.sure_to_delete)\n                        setMessage(context.getString(R.string.prepare_to_delete_s, it.video.title))\n                        setPositiveButton(R.string.confirm) { _, _ ->\n                            // if (file.exists()) file.delete()\n                            HFileManager.getDownloadVideoFolder(\n                                it.video.videoCode\n                            ).deleteRecursively()\n                            fragment.viewModel.deleteDownloadHanimeBy(\n                                it.video.videoCode,\n                                it.video.quality\n                            )\n                        }\n                        setNegativeButton(R.string.cancel, null)\n                    }\n                }\n            }\n            viewHolder.binding.btnLocalPlayback.setOnClickListener {\n                val position = viewHolder.bindingAdapterPosition\n                val item = getItem(position) ?: return@setOnClickListener\n//                context.openDownloadedHanimeVideoLocally(item.video.videoUri, onFileNotFound = {\n//                    context.showAlertDialog {\n//                        setTitle(R.string.video_not_exist)\n//                        setMessage(R.string.video_deleted_sure_to_delete_item)\n//                        setPositiveButton(R.string.delete) { _, _ ->\n//                            fragment.viewModel.deleteDownloadHanimeBy(\n//                                item.video.videoCode,\n//                                item.video.quality\n//                            )\n//                        }\n//                        setNegativeButton(R.string.cancel, null)\n//                    }\n//                })\n                context.openDownloadedHanimeVideoInActivity(item.video.videoCode)\n            }\n            viewHolder.binding.btnExternalPlayback.setOnClickListener {\n                val position = viewHolder.bindingAdapterPosition\n                val item = getItem(position) ?: return@setOnClickListener\n                MediaUtils.playMedia(context, item.video.videoUri)\n            }\n        }\n    }\n}"
  },
  {
    "path": "app/src/main/java/com/yenaly/han1meviewer/ui/adapter/HanimeDownloadingRvAdapter.kt",
    "content": "package com.yenaly.han1meviewer.ui.adapter\n\nimport android.animation.ValueAnimator\nimport android.annotation.SuppressLint\nimport android.content.Context\nimport android.graphics.RenderEffect\nimport android.graphics.Shader\nimport android.os.Build\nimport android.util.Log\nimport android.view.LayoutInflater\nimport android.view.ViewGroup\nimport androidx.annotation.RequiresApi\nimport androidx.core.view.updateLayoutParams\nimport androidx.interpolator.view.animation.FastOutSlowInInterpolator\nimport androidx.recyclerview.widget.DiffUtil\nimport com.chad.library.adapter4.BaseDifferAdapter\nimport com.chad.library.adapter4.viewholder.DataBindingHolder\nimport com.google.android.material.button.MaterialButton\nimport com.yenaly.han1meviewer.R\nimport com.yenaly.han1meviewer.databinding.ItemHanimeDownloadingBinding\nimport com.yenaly.han1meviewer.logic.entity.download.HanimeDownloadEntity\nimport com.yenaly.han1meviewer.logic.state.DownloadState\nimport com.yenaly.han1meviewer.ui.fragment.home.download.DownloadingFragment\nimport com.yenaly.han1meviewer.util.HImageMeower.loadUnhappily\nimport com.yenaly.han1meviewer.util.addUpdateListener\nimport com.yenaly.han1meviewer.util.setStateViewLayout\nimport com.yenaly.han1meviewer.util.showAlertDialog\nimport com.yenaly.han1meviewer.worker.HanimeDownloadManagerV2\nimport com.yenaly.han1meviewer.worker.HanimeDownloadWorker\nimport com.yenaly.yenaly_libs.utils.dpF\nimport com.yenaly.yenaly_libs.utils.formatFileSizeV2\nimport com.yenaly.yenaly_libs.utils.logFieldsChange\nimport java.lang.ref.WeakReference\n\n/**\n * @project Han1meViewer\n * @author Yenaly Liew\n * @time 2023/11/26 026 17:05\n */\nclass HanimeDownloadingRvAdapter(private val fragment: DownloadingFragment) :\n    BaseDifferAdapter<HanimeDownloadEntity, DataBindingHolder<ItemHanimeDownloadingBinding>>(\n        COMPARATOR\n    ) {\n\n    init {\n        isStateViewEnable = true\n    }\n\n    companion object {\n        const val TAG = \"HanimeDownloadingRvAdapter\"\n\n        private val interpolator = FastOutSlowInInterpolator()\n\n        private const val DOWNLOADING = 1\n        private const val STATE = 1 shl 1\n        private const val PROGRESS = 1 shl 2\n\n        val COMPARATOR = object : DiffUtil.ItemCallback<HanimeDownloadEntity>() {\n            override fun areContentsTheSame(\n                oldItem: HanimeDownloadEntity,\n                newItem: HanimeDownloadEntity,\n            ): Boolean {\n                return oldItem == newItem\n            }\n\n            override fun areItemsTheSame(\n                oldItem: HanimeDownloadEntity,\n                newItem: HanimeDownloadEntity,\n            ): Boolean {\n                return oldItem.id == newItem.id\n            }\n\n            override fun getChangePayload(\n                oldItem: HanimeDownloadEntity,\n                newItem: HanimeDownloadEntity,\n            ): Any {\n                logFieldsChange(TAG, oldItem, newItem)\n                var bitset = 0\n                if (oldItem.downloadedLength != newItem.downloadedLength)\n                    bitset = bitset or DOWNLOADING\n                if (oldItem.state != newItem.state)\n                    bitset = bitset or STATE\n                if (oldItem.progress != newItem.progress)\n                    bitset = bitset or PROGRESS\n                return bitset\n            }\n        }\n    }\n\n    @SuppressLint(\"SetTextI18n\")\n    override fun onBindViewHolder(\n        holder: DataBindingHolder<ItemHanimeDownloadingBinding>,\n        position: Int,\n        item: HanimeDownloadEntity?,\n    ) {\n        item ?: return\n        holder.binding.tvTitle.text = item.title\n        holder.binding.ivCover.loadUnhappily(item.coverUri, item.coverUrl)\n        Log.d(TAG, \"调用 load\")\n        holder.itemView.post {\n            holder.binding.vCoverBg.updateLayoutParams {\n                height = holder.itemView.height\n            }\n            holder.binding.ivCoverBg.updateLayoutParams {\n                height = holder.itemView.height\n            }\n        }\n        holder.binding.clProgress.post {\n            holder.binding.vProgress.updateLayoutParams {\n                width = holder.binding.clProgress.width * item.progress / 100\n            }\n        }\n        holder.binding.ivCoverBg.apply {\n            loadUnhappily(item.coverUri, item.coverUrl)\n            if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.S) {\n                val renderEffect = RenderEffect.createBlurEffect(\n                    8.dpF, 8.dpF,\n                    Shader.TileMode.CLAMP\n                )\n                setRenderEffect(renderEffect)\n            }\n        }\n        holder.binding.tvDownloadedSize.text = item.downloadedLength.formatFileSizeV2()\n        holder.binding.tvSize.text = item.length.formatFileSizeV2()\n        holder.binding.tvQuality.text = item.quality\n//        holder.binding.tvProgress.text = \"${item.progress}%\"\n//        holder.binding.pbProgress.setProgress(item.progress, true)\n        holder.binding.btnStart.handleStartButton(item)\n    }\n\n    @SuppressLint(\"SetTextI18n\")\n    override fun onBindViewHolder(\n        holder: DataBindingHolder<ItemHanimeDownloadingBinding>,\n        position: Int,\n        item: HanimeDownloadEntity?,\n        payloads: List<Any>,\n    ) {\n        if (payloads.isEmpty() || payloads.first() == 0)\n            return super.onBindViewHolder(holder, position, item, payloads)\n        item ?: return\n        val bitset = payloads.first() as Int\n        if (bitset and DOWNLOADING != 0) {\n            holder.binding.btnStart.handleStartButton(item)\n            holder.binding.tvDownloadedSize.text = item.downloadedLength.formatFileSizeV2()\n        }\n        if (bitset and STATE != 0) {\n            holder.binding.btnStart.handleStartButton(item)\n        }\n        if (bitset and PROGRESS != 0) {\n//            holder.binding.tvProgress.text = \"${item.progress}%\"\n//            holder.binding.pbProgress.setProgress(item.progress, true)\n\n            // 根据百分比，设置 vProgress 的宽度，比如 50% 就设置成 itemView 50% 的宽度\n            val weakViewProgress = WeakReference(holder.binding.vProgress)\n            ValueAnimator.ofInt(\n                holder.binding.vProgress.width,\n                holder.itemView.width * item.progress / 100\n            ).apply {\n                // must be less than HanimeDownloadWorker.RESPONSE_INTERVAL\n                duration = 300L.coerceAtMost(HanimeDownloadWorker.RESPONSE_INTERVAL)\n                interpolator = HanimeDownloadingRvAdapter.interpolator\n                addUpdateListener(fragment) {\n                    weakViewProgress.get()?.updateLayoutParams {\n                        width = it.animatedValue as Int\n                    }\n                }\n            }.start()\n        }\n    }\n\n    override fun onCreateViewHolder(\n        context: Context,\n        parent: ViewGroup,\n        viewType: Int,\n    ): DataBindingHolder<ItemHanimeDownloadingBinding> {\n        return DataBindingHolder(\n            ItemHanimeDownloadingBinding.inflate(\n                LayoutInflater.from(context), parent, false\n            )\n        ).also { viewHolder ->\n            viewHolder.binding.btnStart.setOnClickListener {\n                val pos = viewHolder.bindingAdapterPosition\n                val item = getItem(pos) ?: return@setOnClickListener\n//                val isDownloading: Boolean\n                if (item.isDownloading) {\n//                    isDownloading = false\n//                    cancelUniqueWorkAndPause(item.copy(isDownloading = false))\n                    HanimeDownloadManagerV2.stopTask(item)\n                } else {\n//                    isDownloading = true\n//                    continueWork(item.copy(isDownloading = true))\n                    HanimeDownloadManagerV2.resumeTask(item)\n                }\n                viewHolder.binding.btnStart.handleStartButton(item, rotate = true)\n            }\n            viewHolder.binding.btnCancel.setOnClickListener {\n                val pos = viewHolder.bindingAdapterPosition\n                val item = getItem(pos) ?: return@setOnClickListener\n                context.showAlertDialog {\n                    setTitle(R.string.sure_to_delete)\n                    setMessage(\n                        context.getString(R.string.prepare_to_delete_s, item.title)\n                    )\n                    setPositiveButton(R.string.confirm) { _, _ ->\n//                        cancelUniqueWorkAndDelete(item)\n                        HanimeDownloadManagerV2.deleteTask(item)\n                    }\n                    setNegativeButton(R.string.cancel, null)\n                }\n            }\n        }\n    }\n\n\n    @SuppressLint(\"SetTextI18n\")\n    private fun MaterialButton.handleStartButton(\n        item: HanimeDownloadEntity,\n        rotate: Boolean = false\n    ) {\n        val state = if (rotate) {\n            when (item.state) {\n                DownloadState.Unknown,\n                DownloadState.Queued,\n                DownloadState.Paused -> DownloadState.Downloading\n\n                DownloadState.Downloading -> DownloadState.Paused\n\n                DownloadState.Finished,\n                DownloadState.Failed -> DownloadState.Unknown\n            }\n        } else {\n            item.state\n        }\n        when (state) {\n            DownloadState.Queued -> {\n                setText(R.string.already_in_queue)\n                setIconResource(R.drawable.ic_baseline_play_arrow_24)\n            }\n\n            DownloadState.Downloading -> {\n//                setText(R.string.pause)\n//                setIconResource(R.drawable.ic_baseline_pause_24)\n                text = \"${item.progress}%\"\n                setIconResource(R.drawable.ic_baseline_pause_24)\n            }\n\n            DownloadState.Paused -> {\n                setText(R.string.continues)\n                setIconResource(R.drawable.ic_baseline_play_arrow_24)\n            }\n\n            DownloadState.Failed -> {\n                setText(R.string.retry)\n                setIconResource(R.drawable.baseline_error_outline_24)\n            }\n\n            DownloadState.Finished, DownloadState.Unknown -> {} // do nothing\n        }\n    }\n}"
  },
  {
    "path": "app/src/main/java/com/yenaly/han1meviewer/ui/adapter/HanimeMyListVideoAdapter.kt",
    "content": "package com.yenaly.han1meviewer.ui.adapter\n\nimport android.content.Context\nimport android.view.View\nimport android.view.ViewGroup\nimport android.widget.ImageView\nimport android.widget.TextView\nimport androidx.recyclerview.widget.DiffUtil\nimport coil.load\nimport com.chad.library.adapter4.BaseDifferAdapter\nimport com.chad.library.adapter4.viewholder.QuickViewHolder\nimport com.yenaly.han1meviewer.R\nimport com.yenaly.han1meviewer.VIDEO_CODE\nimport com.yenaly.han1meviewer.VideoCoverSize\nimport com.yenaly.han1meviewer.logic.model.HanimeInfo\nimport com.yenaly.han1meviewer.ui.activity.VideoActivity\nimport com.yenaly.yenaly_libs.utils.activity\nimport com.yenaly.yenaly_libs.utils.startActivity\n\n/**\n * @project Han1meViewer\n * @author Yenaly Liew\n * @time 2023/11/26 026 16:38\n */\nclass HanimeMyListVideoAdapter : BaseDifferAdapter<HanimeInfo, QuickViewHolder>(COMPARATOR) {\n\n    init {\n        isStateViewEnable = true\n    }\n\n    companion object {\n        val COMPARATOR = object : DiffUtil.ItemCallback<HanimeInfo>() {\n            override fun areItemsTheSame(\n                oldItem: HanimeInfo,\n                newItem: HanimeInfo,\n            ): Boolean {\n                return oldItem.videoCode == newItem.videoCode\n            }\n\n            override fun areContentsTheSame(\n                oldItem: HanimeInfo,\n                newItem: HanimeInfo,\n            ): Boolean {\n                return oldItem == newItem\n            }\n        }\n    }\n\n    override fun onBindViewHolder(holder: QuickViewHolder, position: Int, item: HanimeInfo?) {\n        item ?: return\n        holder.getView<TextView>(R.id.title).text = item.title\n        holder.getView<ImageView>(R.id.cover).load(item.coverUrl) {\n            crossfade(true)\n        }\n    }\n\n    override fun onCreateViewHolder(\n        context: Context,\n        parent: ViewGroup,\n        viewType: Int,\n    ): QuickViewHolder {\n        return QuickViewHolder(R.layout.item_hanime_video_simplified, parent).also { viewHolder ->\n            viewHolder.getView<View>(R.id.frame).layoutParams = ViewGroup.LayoutParams(\n                ViewGroup.LayoutParams.MATCH_PARENT,\n                ViewGroup.LayoutParams.WRAP_CONTENT\n            )\n            viewHolder.getView<ImageView>(R.id.cover).scaleType = ImageView.ScaleType.CENTER_CROP\n            viewHolder.itemView.apply {\n                setOnClickListener {\n                    val position = viewHolder.bindingAdapterPosition\n                    val item = getItem(position) ?: return@setOnClickListener\n                    val videoCode = item.videoCode\n                    context.activity?.startActivity<VideoActivity>(VIDEO_CODE to videoCode)\n                }\n                // setOnLongClickListener 由各自的 Fragment 实现\n            }\n            with(VideoCoverSize.Simplified) {\n                viewHolder.getView<ViewGroup>(R.id.cover_wrapper).resizeForVideoCover()\n            }\n        }\n    }\n}"
  },
  {
    "path": "app/src/main/java/com/yenaly/han1meviewer/ui/adapter/HanimePreviewNewsRvAdapter.kt",
    "content": "package com.yenaly.han1meviewer.ui.adapter\n\nimport android.content.Context\nimport android.view.LayoutInflater\nimport android.view.ViewGroup\nimport android.widget.ImageView\nimport androidx.recyclerview.widget.LinearLayoutManager\nimport coil.load\nimport com.chad.library.adapter4.BaseQuickAdapter\nimport com.chad.library.adapter4.viewholder.DataBindingHolder\nimport com.chad.library.adapter4.viewholder.QuickViewHolder\nimport com.lxj.xpopup.XPopup\nimport com.yenaly.han1meviewer.R\nimport com.yenaly.han1meviewer.VIDEO_CODE\nimport com.yenaly.han1meviewer.databinding.ItemHanimePreviewNewsV2Binding\nimport com.yenaly.han1meviewer.logic.model.HanimePreview\nimport com.yenaly.han1meviewer.ui.activity.PreviewActivity\nimport com.yenaly.han1meviewer.ui.activity.VideoActivity\nimport com.yenaly.han1meviewer.ui.popup.CoilImageLoader\nimport com.yenaly.han1meviewer.ui.view.BlurTransformation\nimport com.yenaly.yenaly_libs.utils.startActivity\n\n/**\n * @project Han1meViewer\n * @author Yenaly Liew\n * @time 2023/11/26 026 16:48\n */\nclass HanimePreviewNewsRvAdapter :\n    BaseQuickAdapter<HanimePreview.PreviewInfo, DataBindingHolder<ItemHanimePreviewNewsV2Binding>>() {\n\n    init {\n        isStateViewEnable = true\n    }\n\n    private val imageLoader = CoilImageLoader()\n\n    override fun onBindViewHolder(\n        holder: DataBindingHolder<ItemHanimePreviewNewsV2Binding>,\n        position: Int,\n        item: HanimePreview.PreviewInfo?,\n    ) {\n        item ?: return\n        holder.binding.ivCoverBig.load(item.coverUrl) {\n            crossfade(true)\n            transformations(BlurTransformation(context))\n        }\n        holder.binding.tvTitle.text = item.title\n        holder.binding.tvIntroduction.text = item.introduction\n        holder.binding.tvBrand.text = item.brand\n        holder.binding.tvReleaseDate.text = item.releaseDate\n        holder.binding.tvVideoTitle.text = item.videoTitle\n\n        holder.binding.tags.tags = item.tags\n\n        holder.binding.rvPreview.apply {\n            layoutManager = LinearLayoutManager(context, LinearLayoutManager.HORIZONTAL, false)\n            adapter = PreviewPicRvAdapter(item)\n        }\n    }\n\n    override fun onCreateViewHolder(\n        context: Context,\n        parent: ViewGroup,\n        viewType: Int,\n    ): DataBindingHolder<ItemHanimePreviewNewsV2Binding> {\n        return DataBindingHolder(\n            ItemHanimePreviewNewsV2Binding.inflate(\n                LayoutInflater.from(context), parent, false\n            )\n        ).also { viewHolder ->\n            viewHolder.binding.tags.lifecycle = (context as? PreviewActivity)?.lifecycle\n            viewHolder.binding.tags.isCollapsedEnabled = true\n            viewHolder.itemView.apply {\n                setOnClickListener {\n                    val position = viewHolder.bindingAdapterPosition\n                    val item = getItem(position) ?: return@setOnClickListener\n                    if (context is PreviewActivity) {\n                        context.startActivity<VideoActivity>(VIDEO_CODE to item.videoCode)\n                    }\n                }\n            }\n        }\n    }\n\n    private inner class PreviewPicRvAdapter(private val item: HanimePreview.PreviewInfo) :\n        BaseQuickAdapter<String, QuickViewHolder>(item.relatedPicsUrl) {\n        override fun onBindViewHolder(holder: QuickViewHolder, position: Int, item: String?) {\n            holder.getView<ImageView>(R.id.iv_preview_news_pic).load(item) {\n                crossfade(true)\n            }\n        }\n\n        override fun onCreateViewHolder(\n            context: Context,\n            parent: ViewGroup,\n            viewType: Int,\n        ): QuickViewHolder {\n            return QuickViewHolder(\n                R.layout.item_hanime_preview_news_pic, parent\n            ).also { viewHolder ->\n                viewHolder.itemView.setOnClickListener {\n                    val position = viewHolder.bindingAdapterPosition\n                    XPopup.Builder(context).asImageViewer(\n                        it as? ImageView, position, item.relatedPicsUrl, { popupView, pos ->\n                            popupView.updateSrcView(recyclerView.getChildAt(pos) as? ImageView)\n                        }, imageLoader\n                    ).show()\n                }\n            }\n        }\n    }\n}"
  },
  {
    "path": "app/src/main/java/com/yenaly/han1meviewer/ui/adapter/HanimePreviewTourRvAdapter.kt",
    "content": "package com.yenaly.han1meviewer.ui.adapter\n\nimport android.content.Context\nimport android.view.ViewGroup\nimport android.widget.ImageView\nimport coil.load\nimport com.chad.library.adapter4.BaseDifferAdapter\nimport com.chad.library.adapter4.viewholder.QuickViewHolder\nimport com.lxj.xpopup.XPopup\nimport com.yenaly.han1meviewer.R\nimport com.yenaly.han1meviewer.logic.model.HanimeInfo\nimport com.yenaly.han1meviewer.ui.popup.CoilImageLoader\n\n/**\n * @project Han1meViewer\n * @author Yenaly Liew\n * @time 2023/11/29 029 20:11\n */\nclass HanimePreviewTourRvAdapter : BaseDifferAdapter<HanimeInfo, QuickViewHolder>(\n    HanimeVideoRvAdapter.COMPARATOR\n) {\n\n    private val imageLoader = CoilImageLoader()\n\n    override fun onBindViewHolder(\n        holder: QuickViewHolder,\n        position: Int,\n        item: HanimeInfo?,\n    ) {\n        holder.getView<ImageView>(R.id.cover).load(item?.coverUrl) {\n            crossfade(true)\n        }\n    }\n\n    override fun onCreateViewHolder(\n        context: Context,\n        parent: ViewGroup,\n        viewType: Int,\n    ): QuickViewHolder {\n        return QuickViewHolder(R.layout.item_preview_tour_simplified, parent).also { viewHolder ->\n            viewHolder.itemView.setOnLongClickListener {\n                val position = viewHolder.bindingAdapterPosition\n                val urlList = items.map { it.coverUrl }\n                XPopup.Builder(context).asImageViewer(\n                    viewHolder.getView(R.id.cover), position, urlList, { popupView, pos ->\n                        popupView.updateSrcView(recyclerView.getChildAt(pos) as? ImageView)\n                    }, imageLoader\n                ).show()\n                return@setOnLongClickListener true\n            }\n        }\n    }\n}"
  },
  {
    "path": "app/src/main/java/com/yenaly/han1meviewer/ui/adapter/HanimeSearchHistoryRvAdapter.kt",
    "content": "package com.yenaly.han1meviewer.ui.adapter\n\nimport android.content.Context\nimport android.view.View\nimport android.view.ViewGroup\nimport androidx.recyclerview.widget.DiffUtil\nimport com.chad.library.adapter4.BaseDifferAdapter\nimport com.chad.library.adapter4.viewholder.QuickViewHolder\nimport com.yenaly.han1meviewer.R\nimport com.yenaly.han1meviewer.logic.entity.SearchHistoryEntity\n\n/**\n * @project Han1meViewer\n * @author Yenaly Liew\n * @time 2023/11/26 026 16:42\n */\nclass HanimeSearchHistoryRvAdapter :\n    BaseDifferAdapter<SearchHistoryEntity, QuickViewHolder>(COMPARATOR) {\n\n    init {\n        isStateViewEnable = true\n    }\n\n    companion object {\n        val COMPARATOR = object : DiffUtil.ItemCallback<SearchHistoryEntity>() {\n            override fun areItemsTheSame(\n                oldItem: SearchHistoryEntity,\n                newItem: SearchHistoryEntity,\n            ): Boolean {\n                return oldItem.id == newItem.id\n            }\n\n            override fun areContentsTheSame(\n                oldItem: SearchHistoryEntity,\n                newItem: SearchHistoryEntity,\n            ): Boolean {\n                return oldItem == newItem\n            }\n        }\n    }\n\n    var listener: OnItemViewClickListener? = null\n\n    override fun onBindViewHolder(\n        holder: QuickViewHolder,\n        position: Int,\n        item: SearchHistoryEntity?,\n    ) {\n        item ?: return\n        holder.setText(R.id.tv_text, item.query)\n    }\n\n    override fun onCreateViewHolder(\n        context: Context,\n        parent: ViewGroup,\n        viewType: Int,\n    ): QuickViewHolder {\n        return QuickViewHolder(R.layout.item_search_history, parent).also { viewHolder ->\n            viewHolder.getView<View>(R.id.btn_remove).setOnClickListener {\n                // #issue-142: 部分机型调用 getItem().notNull() 可能会报错\n                listener?.onItemRemoveListener(\n                    it, getItem(viewHolder.bindingAdapterPosition)\n                )\n            }\n            viewHolder.getView<View>(R.id.root).setOnClickListener {\n                listener?.onItemClickListener(\n                    it, getItem(viewHolder.bindingAdapterPosition)\n                )\n            }\n        }\n    }\n\n    interface OnItemViewClickListener {\n        fun onItemClickListener(v: View, history: SearchHistoryEntity?)\n        fun onItemRemoveListener(v: View, history: SearchHistoryEntity?)\n    }\n}"
  },
  {
    "path": "app/src/main/java/com/yenaly/han1meviewer/ui/adapter/HanimeUpdateRvAdapter.kt",
    "content": "package com.yenaly.han1meviewer.ui.adapter\n\nimport android.animation.ValueAnimator\nimport android.annotation.SuppressLint\nimport android.content.Context\nimport android.util.Log\nimport android.view.LayoutInflater\nimport android.view.ViewGroup\nimport androidx.core.view.updateLayoutParams\nimport androidx.interpolator.view.animation.FastOutSlowInInterpolator\nimport androidx.recyclerview.widget.DiffUtil\nimport com.chad.library.adapter4.BaseDifferAdapter\nimport com.chad.library.adapter4.viewholder.DataBindingHolder\nimport com.google.android.material.button.MaterialButton\nimport com.yenaly.han1meviewer.R\nimport com.yenaly.han1meviewer.databinding.ItemHanimeUpdatingBinding\nimport com.yenaly.han1meviewer.logic.entity.download.HUpdateEntity\nimport com.yenaly.han1meviewer.logic.state.DownloadState\nimport com.yenaly.han1meviewer.ui.fragment.home.download.DownloadingFragment\nimport com.yenaly.han1meviewer.util.addUpdateListener\nimport com.yenaly.han1meviewer.util.showAlertDialog\nimport com.yenaly.han1meviewer.worker.HUpdateWorker\nimport com.yenaly.han1meviewer.worker.HanimeDownloadWorker\nimport com.yenaly.yenaly_libs.utils.formatFileSizeV2\nimport com.yenaly.yenaly_libs.utils.logFieldsChange\nimport java.lang.ref.WeakReference\n\n/**\n * @project Han1meViewer\n * @author Yenaly Liew\n * @time 2023/11/26 026 17:05\n */\nclass HanimeUpdateRvAdapter(private val fragment: DownloadingFragment) :\n    BaseDifferAdapter<HUpdateEntity, DataBindingHolder<ItemHanimeUpdatingBinding>>(\n        COMPARATOR\n    ) {\n\n    init {\n        isStateViewEnable = true\n    }\n\n    companion object {\n        const val TAG = \"HanimeUpdateRvAdapter\"\n\n        private val interpolator = FastOutSlowInInterpolator()\n\n        private const val DOWNLOADING = 1\n        private const val STATE = 1 shl 1\n        private const val PROGRESS = 1 shl 2\n\n        val COMPARATOR = object : DiffUtil.ItemCallback<HUpdateEntity>() {\n            override fun areContentsTheSame(\n                oldItem: HUpdateEntity,\n                newItem: HUpdateEntity,\n            ): Boolean {\n                return oldItem == newItem\n            }\n\n            override fun areItemsTheSame(\n                oldItem: HUpdateEntity,\n                newItem: HUpdateEntity,\n            ): Boolean {\n                return oldItem.id == newItem.id\n            }\n\n            override fun getChangePayload(\n                oldItem: HUpdateEntity,\n                newItem: HUpdateEntity,\n            ): Any {\n                logFieldsChange(TAG, oldItem, newItem)\n                var bitset = 0\n                if (oldItem.downloadedLength != newItem.downloadedLength)\n                    bitset = bitset or DOWNLOADING\n                if (oldItem.state != newItem.state)\n                    bitset = bitset or STATE\n                if (oldItem.progress != newItem.progress)\n                    bitset = bitset or PROGRESS\n                return bitset\n            }\n        }\n    }\n\n    @SuppressLint(\"SetTextI18n\")\n    override fun onBindViewHolder(\n        holder: DataBindingHolder<ItemHanimeUpdatingBinding>,\n        position: Int,\n        item: HUpdateEntity?,\n    ) {\n        item ?: return\n        holder.binding.tvTitle.text = item.name\n        Log.d(TAG, \"调用 load\")\n        holder.itemView.post {\n            holder.binding.vCoverBg.updateLayoutParams {\n                height = holder.itemView.height\n            }\n            holder.binding.ivCoverBg.updateLayoutParams {\n                height = holder.itemView.height\n            }\n        }\n        holder.binding.clProgress.post {\n            holder.binding.vProgress.updateLayoutParams {\n                width = holder.binding.clProgress.width * item.progress / 100\n            }\n        }\n        holder.binding.tvDownloadedSize.text = item.downloadedLength.formatFileSizeV2()\n        holder.binding.tvSize.text = item.length.formatFileSizeV2()\n\n        holder.binding.btnStart.handleStartButton(item)\n    }\n\n    @SuppressLint(\"SetTextI18n\")\n    override fun onBindViewHolder(\n        holder: DataBindingHolder<ItemHanimeUpdatingBinding>,\n        position: Int,\n        item: HUpdateEntity?,\n        payloads: List<Any>,\n    ) {\n        if (payloads.isEmpty() || payloads.first() == 0)\n            return super.onBindViewHolder(holder, position, item, payloads)\n        item ?: return\n        val bitset = payloads.first() as Int\n        if (bitset and DOWNLOADING != 0) {\n            holder.binding.btnStart.handleStartButton(item)\n            holder.binding.tvDownloadedSize.text = item.downloadedLength.formatFileSizeV2()\n        }\n        if (bitset and STATE != 0) {\n            holder.binding.btnStart.handleStartButton(item)\n        }\n        if (bitset and PROGRESS != 0) {\n            // 根据百分比，设置 vProgress 的宽度，比如 50% 就设置成 itemView 50% 的宽度\n            val weakViewProgress = WeakReference(holder.binding.vProgress)\n            ValueAnimator.ofInt(\n                holder.binding.vProgress.width,\n                holder.itemView.width * item.progress / 100\n            ).apply {\n                // must be less than HanimeDownloadWorker.RESPONSE_INTERVAL\n                duration = 300L.coerceAtMost(HanimeDownloadWorker.RESPONSE_INTERVAL)\n                interpolator = interpolator\n                addUpdateListener(fragment) {\n                    weakViewProgress.get()?.updateLayoutParams {\n                        width = it.animatedValue as Int\n                    }\n                }\n            }.start()\n        }\n    }\n\n    override fun onCreateViewHolder(\n        context: Context,\n        parent: ViewGroup,\n        viewType: Int,\n    ): DataBindingHolder<ItemHanimeUpdatingBinding> {\n        return DataBindingHolder(\n            ItemHanimeUpdatingBinding.inflate(\n                LayoutInflater.from(context), parent, false\n            )\n        ).also { viewHolder ->\n            viewHolder.binding.btnStart.setOnClickListener {\n                val pos = viewHolder.bindingAdapterPosition\n                val item = getItem(pos) ?: return@setOnClickListener\n                if (item.isDownloading) {\n                    HUpdateWorker.stop()\n                } else {\n                    HUpdateWorker.resume()\n                }\n                viewHolder.binding.btnStart.handleStartButton(item, rotate = true)\n            }\n            viewHolder.binding.btnCancel.setOnClickListener {\n                val pos = viewHolder.bindingAdapterPosition\n                val item = getItem(pos) ?: return@setOnClickListener\n                context.showAlertDialog {\n                    setTitle(R.string.sure_to_delete)\n                    setMessage(\n                        context.getString(R.string.prepare_to_delete_s, item.name)\n                    )\n                    setPositiveButton(R.string.confirm) { _, _ ->\n                        HUpdateWorker.deleteUpdate(context)\n                    }\n                    setNegativeButton(R.string.cancel, null)\n                }\n            }\n        }\n    }\n\n\n    @SuppressLint(\"SetTextI18n\")\n    private fun MaterialButton.handleStartButton(\n        item: HUpdateEntity,\n        rotate: Boolean = false\n    ) {\n        val state = if (rotate) {\n            when (item.state) {\n                DownloadState.Unknown,\n                DownloadState.Queued,\n                DownloadState.Paused -> DownloadState.Downloading\n\n                DownloadState.Downloading -> DownloadState.Paused\n\n                DownloadState.Finished,\n                DownloadState.Failed -> DownloadState.Unknown\n            }\n        } else {\n            item.state\n        }\n        when (state) {\n            DownloadState.Queued -> {\n                setText(R.string.already_in_queue)\n                setIconResource(R.drawable.ic_baseline_play_arrow_24)\n            }\n\n            DownloadState.Downloading -> {\n//                setText(R.string.pause)\n//                setIconResource(R.drawable.ic_baseline_pause_24)\n                text = \"${item.progress}%\"\n                setIconResource(R.drawable.ic_baseline_pause_24)\n            }\n\n            DownloadState.Paused -> {\n                setText(R.string.continues)\n                setIconResource(R.drawable.ic_baseline_play_arrow_24)\n            }\n\n            DownloadState.Failed -> {\n                setText(R.string.retry)\n                setIconResource(R.drawable.baseline_error_outline_24)\n            }\n\n            DownloadState.Finished, DownloadState.Unknown -> {} // do nothing\n        }\n    }\n}"
  },
  {
    "path": "app/src/main/java/com/yenaly/han1meviewer/ui/adapter/HanimeUpdatedRvAdapter.kt",
    "content": "package com.yenaly.han1meviewer.ui.adapter\n\nimport android.content.Context\nimport android.graphics.RenderEffect\nimport android.graphics.Shader\nimport android.os.Build\nimport android.view.LayoutInflater\nimport android.view.ViewGroup\nimport androidx.core.net.toFile\nimport androidx.core.net.toUri\nimport androidx.core.view.updateLayoutParams\nimport androidx.recyclerview.widget.DiffUtil\nimport com.chad.library.adapter4.BaseDifferAdapter\nimport com.chad.library.adapter4.viewholder.DataBindingHolder\nimport com.yenaly.han1meviewer.HFileManager\nimport com.yenaly.han1meviewer.LOCAL_DATE_TIME_FORMAT\nimport com.yenaly.han1meviewer.R\nimport com.yenaly.han1meviewer.VIDEO_CODE\nimport com.yenaly.han1meviewer.databinding.ItemHanimeUpdatedBinding\nimport com.yenaly.han1meviewer.logic.entity.download.HUpdateEntity\nimport com.yenaly.han1meviewer.ui.activity.VideoActivity\nimport com.yenaly.han1meviewer.ui.fragment.home.download.DownloadedFragment\nimport com.yenaly.han1meviewer.util.HImageMeower.loadUnhappily\nimport com.yenaly.han1meviewer.util.MediaUtils\nimport com.yenaly.han1meviewer.util.installApkPackage\nimport com.yenaly.han1meviewer.util.openDownloadedHanimeVideoInActivity\nimport com.yenaly.han1meviewer.util.showAlertDialog\nimport com.yenaly.han1meviewer.util.updateFile\nimport com.yenaly.han1meviewer.worker.HUpdateWorker\nimport com.yenaly.yenaly_libs.utils.activity\nimport com.yenaly.yenaly_libs.utils.dpF\nimport com.yenaly.yenaly_libs.utils.formatFileSizeV2\nimport com.yenaly.yenaly_libs.utils.startActivity\nimport kotlinx.coroutines.CoroutineScope\nimport kotlinx.coroutines.Dispatchers\nimport kotlinx.coroutines.SupervisorJob\nimport kotlinx.coroutines.launch\nimport kotlinx.datetime.Instant\nimport kotlinx.datetime.TimeZone\nimport kotlinx.datetime.format\nimport kotlinx.datetime.toLocalDateTime\n\n/**\n * @project Han1meViewer\n * @author Yenaly Liew\n * @time 2023/11/26 026 16:57\n */\nclass HanimeUpdatedRvAdapter(private val fragment: DownloadedFragment) :\n    BaseDifferAdapter<HUpdateEntity, DataBindingHolder<ItemHanimeUpdatedBinding>>(\n        COMPARATOR\n    ) {\n\n    init {\n        isStateViewEnable = true\n    }\n\n    companion object {\n        val scope = CoroutineScope(SupervisorJob() + Dispatchers.IO)\n\n        val COMPARATOR = object : DiffUtil.ItemCallback<HUpdateEntity>() {\n            override fun areContentsTheSame(\n                oldItem: HUpdateEntity,\n                newItem: HUpdateEntity,\n            ): Boolean {\n                return oldItem == newItem\n            }\n\n            override fun areItemsTheSame(\n                oldItem: HUpdateEntity,\n                newItem: HUpdateEntity,\n            ): Boolean {\n                return oldItem.id == newItem.id\n            }\n        }\n    }\n\n    override fun onBindViewHolder(\n        holder: DataBindingHolder<ItemHanimeUpdatedBinding>,\n        position: Int,\n        item: HUpdateEntity?,\n    ) {\n        item ?: return\n        holder.binding.tvTitle.text = item.name\n        holder.itemView.post {\n            // fast path\n            if (holder.itemView.height == holder.binding.vCoverBg.height) return@post\n            holder.binding.vCoverBg.updateLayoutParams {\n                height = holder.itemView.height\n            }\n            holder.binding.ivCoverBg.updateLayoutParams {\n                height = holder.itemView.height\n            }\n        }\n        val realSize = context.updateFile.length()\n        holder.binding.tvSize.text = if (realSize == 0L) {\n            \"???\"\n        } else {\n            item.length.formatFileSizeV2()\n        }\n    }\n\n    override fun onCreateViewHolder(\n        context: Context,\n        parent: ViewGroup,\n        viewType: Int,\n    ): DataBindingHolder<ItemHanimeUpdatedBinding> {\n        return DataBindingHolder(\n            ItemHanimeUpdatedBinding.inflate(\n                LayoutInflater.from(context), parent, false\n            )\n        ).also { viewHolder ->\n            viewHolder.binding.btnDelete.setOnClickListener {\n                val position = viewHolder.bindingAdapterPosition\n                // #issue-158: 这里可能为空\n                val item = getItem(position)\n                item?.let {\n                    context.showAlertDialog {\n                        setTitle(R.string.sure_to_delete)\n                        setMessage(context.getString(R.string.prepare_to_delete_s, it.name))\n                        setPositiveButton(R.string.confirm) { _, _ ->\n                            HUpdateWorker.deleteUpdate(context)\n                        }\n                        setNegativeButton(R.string.cancel, null)\n                    }\n                }\n            }\n            viewHolder.binding.btnInstall.setOnClickListener {\n                scope.launch {\n                    context.installApkPackage(context.updateFile)\n                }\n            }\n        }\n    }\n}"
  },
  {
    "path": "app/src/main/java/com/yenaly/han1meviewer/ui/adapter/HanimeVideoRvAdapter.kt",
    "content": "package com.yenaly.han1meviewer.ui.adapter\n\nimport android.content.Context\nimport android.content.Intent\nimport android.graphics.Color\nimport android.view.View\nimport android.view.ViewGroup\nimport android.widget.ImageView\nimport android.widget.TextView\nimport androidx.core.view.isGone\nimport androidx.core.view.isVisible\nimport androidx.recyclerview.widget.DiffUtil\nimport coil.load\nimport com.chad.library.adapter4.BaseDifferAdapter\nimport com.chad.library.adapter4.viewholder.QuickViewHolder\nimport com.itxca.spannablex.spannable\nimport com.yenaly.han1meviewer.R\nimport com.yenaly.han1meviewer.VIDEO_CODE\nimport com.yenaly.han1meviewer.VIDEO_LAYOUT_MATCH_PARENT\nimport com.yenaly.han1meviewer.VIDEO_LAYOUT_WRAP_CONTENT\nimport com.yenaly.han1meviewer.VideoCoverSize\nimport com.yenaly.han1meviewer.getHanimeShareText\nimport com.yenaly.han1meviewer.logic.model.HanimeInfo\nimport com.yenaly.han1meviewer.ui.activity.MainActivity\nimport com.yenaly.han1meviewer.ui.activity.PreviewActivity\nimport com.yenaly.han1meviewer.ui.activity.SearchActivity\nimport com.yenaly.han1meviewer.ui.activity.VideoActivity\nimport com.yenaly.han1meviewer.ui.fragment.home.HomePageFragment\nimport com.yenaly.yenaly_libs.utils.activity\nimport com.yenaly.yenaly_libs.utils.copyTextToClipboard\nimport com.yenaly.yenaly_libs.utils.dp\nimport com.yenaly.yenaly_libs.utils.showShortToast\nimport com.yenaly.yenaly_libs.utils.startActivity\n\n/**\n * @project Han1meViewer\n * @author Yenaly Liew\n * @time 2023/11/26 026 17:15\n */\nclass HanimeVideoRvAdapter(private val videoWidthType: Int = -1) : // videoWidthType is VIDEO_LAYOUT_MATCH_PARENT or VIDEO_LAYOUT_WRAP_CONTENT or nothing\n    BaseDifferAdapter<HanimeInfo, QuickViewHolder>(COMPARATOR) {\n\n    init {\n        isStateViewEnable = true\n    }\n\n    companion object {\n        val COMPARATOR = object : DiffUtil.ItemCallback<HanimeInfo>() {\n            override fun areItemsTheSame(\n                oldItem: HanimeInfo,\n                newItem: HanimeInfo,\n            ): Boolean {\n                return oldItem.videoCode == newItem.videoCode\n            }\n\n            override fun areContentsTheSame(\n                oldItem: HanimeInfo,\n                newItem: HanimeInfo,\n            ): Boolean {\n                return oldItem == newItem\n            }\n        }\n    }\n\n    override fun getItemViewType(position: Int, list: List<HanimeInfo>): Int {\n        return list[position].itemType\n    }\n\n    override fun onBindViewHolder(holder: QuickViewHolder, position: Int, item: HanimeInfo?) {\n        item ?: return\n        // stackoverflow-64362192\n        when (getItemViewType(position)) {\n            HanimeInfo.SIMPLIFIED -> {\n                holder.getView<ImageView>(R.id.cover).load(item.coverUrl) {\n                    crossfade(true)\n                }\n                holder.getView<TextView>(R.id.title).text = item.title\n            }\n\n            HanimeInfo.NORMAL -> {\n                holder.getView<TextView>(R.id.title).text = item.title\n                holder.getView<ImageView>(R.id.cover).load(item.coverUrl) {\n                    crossfade(true)\n                }\n                holder.getView<TextView>(R.id.is_playing).isVisible = item.isPlaying\n                holder.getView<TextView>(R.id.duration).text = item.duration\n                holder.getView<TextView>(R.id.time).apply {\n                    if (item.uploadTime != null) {\n                        holder.getView<View>(R.id.icon_time).isGone = false\n                        text = item.uploadTime\n                    } else {\n                        holder.getView<View>(R.id.icon_time).isGone = true\n                    }\n                }\n                holder.getView<TextView>(R.id.views).apply {\n                    if (item.views != null) {\n                        holder.getView<View>(R.id.icon_views).isGone = false\n                        text = item.views\n                    } else {\n                        holder.getView<View>(R.id.icon_views).isGone = true\n                    }\n                }\n                holder.getView<TextView>(R.id.genre_and_uploader).apply {\n                    if (item.genre == null && item.uploader == null) {\n                        isGone = true\n                        return@apply\n                    }\n                    isGone = false\n                    text = spannable {\n                        item.genre.span {\n                            margin(4.dp)\n                            when (item.genre) {\n                                \"3D\" -> color(Color.rgb(245, 171, 53))\n                                \"COS\" -> color(Color.rgb(165, 55, 253))\n                                \"同人\" -> color(Color.rgb(241, 130, 141))\n                                else -> color(Color.RED)\n                            }\n                        }\n                        item.uploader.text()\n                    }\n                }\n            }\n        }\n    }\n\n    override fun onCreateViewHolder(\n        context: Context,\n        parent: ViewGroup,\n        viewType: Int,\n    ): QuickViewHolder {\n        return if (viewType == HanimeInfo.NORMAL) {\n            QuickViewHolder(R.layout.item_hanime_video, parent)\n        } else {\n            QuickViewHolder(R.layout.item_hanime_video_simplified, parent)\n        }.also { viewHolder ->\n            when (viewType) {\n                HanimeInfo.SIMPLIFIED -> {\n                    when (context) {\n                        is SearchActivity -> {\n                            viewHolder.getView<View>(R.id.frame).widthMatchParent()\n                        }\n\n                        is VideoActivity -> when (videoWidthType) {\n                            VIDEO_LAYOUT_MATCH_PARENT ->\n                                viewHolder.getView<View>(R.id.frame).widthMatchParent()\n\n                            VIDEO_LAYOUT_WRAP_CONTENT ->\n                                viewHolder.getView<View>(R.id.frame).widthWrapContent()\n                        }\n                    }\n                    with(VideoCoverSize.Simplified) {\n                        viewHolder.getView<ViewGroup>(R.id.cover_wrapper).resizeForVideoCover()\n                    }\n                }\n\n                HanimeInfo.NORMAL -> {\n                    when (context) {\n                        is VideoActivity -> when (videoWidthType) {\n                            VIDEO_LAYOUT_MATCH_PARENT ->\n                                viewHolder.getView<View>(R.id.frame).widthMatchParent()\n\n                            VIDEO_LAYOUT_WRAP_CONTENT ->\n                                viewHolder.getView<View>(R.id.frame).widthWrapContent()\n                        }\n\n                        is MainActivity -> {\n                            val activity = context\n                            val fragment = activity.currentFragment\n                            if (fragment is HomePageFragment) {\n                                viewHolder.getView<View>(R.id.frame).widthWrapContent()\n                            }\n                        }\n                    }\n                    with(VideoCoverSize.Normal) {\n                        viewHolder.getView<ViewGroup>(R.id.cover_wrapper).resizeForVideoCover()\n                    }\n                }\n            }\n            viewHolder.itemView.apply {\n                if (context !is PreviewActivity) {\n                    setOnClickListener {\n                        val position = viewHolder.bindingAdapterPosition\n                        val item = getItem(position) ?: return@setOnClickListener\n                        if (item.isPlaying) {\n                            showShortToast(R.string.watching_this_video_now)\n                        } else {\n                            val videoCode = item.videoCode\n                            context.startVideoActivity(videoCode)\n                        }\n                    }\n                    setOnLongClickListener {\n                        val position = viewHolder.bindingAdapterPosition\n                        val item = getItem(position) ?: return@setOnLongClickListener true\n                        copyTextToClipboard(getHanimeShareText(item.title, item.videoCode))\n                        showShortToast(R.string.copy_to_clipboard)\n                        return@setOnLongClickListener true\n                    }\n                }\n            }\n        }\n    }\n\n    private fun View.widthMatchParent() = apply {\n        layoutParams = ViewGroup.LayoutParams(\n            ViewGroup.LayoutParams.MATCH_PARENT,\n            ViewGroup.LayoutParams.WRAP_CONTENT\n        )\n    }\n\n    private fun View.widthWrapContent() = apply {\n        layoutParams = ViewGroup.LayoutParams(\n            ViewGroup.LayoutParams.WRAP_CONTENT,\n            ViewGroup.LayoutParams.WRAP_CONTENT\n        )\n    }\n\n    private fun Context.startVideoActivity(videoCode: String) {\n        if (this is SearchActivity) {\n            val intent = Intent(this, VideoActivity::class.java).apply {\n                putExtra(VIDEO_CODE, videoCode)\n            }\n            this.subscribeLauncher.launch(intent)\n            return\n        }\n        activity?.startActivity<VideoActivity>(VIDEO_CODE to videoCode)\n    }\n}"
  },
  {
    "path": "app/src/main/java/com/yenaly/han1meviewer/ui/adapter/PlaylistRvAdapter.kt",
    "content": "package com.yenaly.han1meviewer.ui.adapter\n\nimport android.content.Context\nimport android.view.ViewGroup\nimport androidx.fragment.app.Fragment\nimport com.chad.library.adapter4.BaseQuickAdapter\nimport com.chad.library.adapter4.viewholder.QuickViewHolder\nimport com.yenaly.han1meviewer.R\nimport com.yenaly.han1meviewer.logic.model.Playlists\nimport com.yenaly.han1meviewer.ui.fragment.home.MyPlaylistFragment\n\n/**\n * @project Han1meViewer\n * @author Yenaly Liew\n * @time 2023/11/26 026 16:30\n */\nclass PlaylistRvAdapter(private val fragment: Fragment) :\n    BaseQuickAdapter<Playlists.Playlist, QuickViewHolder>() {\n\n    init {\n        isStateViewEnable = true\n    }\n\n    override fun onBindViewHolder(\n        holder: QuickViewHolder,\n        position: Int,\n        item: Playlists.Playlist?,\n    ) {\n        item ?: return\n        holder.setText(R.id.tv_title, item.title)\n        holder.setText(R.id.tv_count, item.total.toString())\n    }\n\n    override fun onCreateViewHolder(\n        context: Context,\n        parent: ViewGroup,\n        viewType: Int,\n    ): QuickViewHolder {\n        return QuickViewHolder(R.layout.item_playlist, parent).also { viewHolder ->\n            check(fragment is MyPlaylistFragment)\n            viewHolder.itemView.setOnClickListener {\n                val position = viewHolder.bindingAdapterPosition\n                val item = getItem(position) ?: return@setOnClickListener\n                fragment.listCode = item.listCode\n                fragment.listTitle = item.title\n                fragment.getNewPlaylistItems()\n                fragment.binding.dlPlaylist.closeDrawers()\n            }\n        }\n    }\n}"
  },
  {
    "path": "app/src/main/java/com/yenaly/han1meviewer/ui/adapter/RvWrapper.kt",
    "content": "package com.yenaly.han1meviewer.ui.adapter\n\nimport android.content.Context\nimport android.graphics.Outline\nimport android.view.View\nimport android.view.ViewGroup\nimport android.view.ViewOutlineProvider\nimport androidx.annotation.StringRes\nimport androidx.core.view.minusAssign\nimport androidx.core.view.plusAssign\nimport androidx.recyclerview.widget.LinearLayoutManager\nimport androidx.recyclerview.widget.RecyclerView\nimport androidx.recyclerview.widget.RecyclerView.LayoutManager\nimport androidx.recyclerview.widget.RecyclerView.ViewHolder\nimport com.chad.library.adapter4.BaseSingleItemAdapter\nimport com.chad.library.adapter4.viewholder.QuickViewHolder\nimport com.yenaly.han1meviewer.R\nimport com.yenaly.han1meviewer.util.dpToPx\n\n/**\n * RecyclerView Wrapper，为了让多 LayoutManager 布局能够在 ConcatAdapter 中使用。\n *\n * @project Han1meViewer\n * @author Yenaly Liew\n * @time 2023/11/27 027 17:00\n */\nclass RvWrapper<VH : ViewHolder>(\n    private val adapter: RecyclerView.Adapter<VH>,\n    private val customRv: ((Context) -> RecyclerView)?,\n    private val layoutManager: () -> LayoutManager,\n) : BaseSingleItemAdapter<Unit, QuickViewHolder>() {\n    companion object {\n        fun <VH : ViewHolder> RecyclerView.Adapter<VH>.wrappedWith(\n            customRv: ((Context) -> RecyclerView)? = null,\n            layoutManager: () -> LayoutManager,\n        ) = RvWrapper(this, customRv, layoutManager)\n    }\n\n    var wrapper: RecyclerView? = null\n        private set\n\n    private var onWrap: (RecyclerView.() -> Unit)? = null\n\n    fun doOnWrap(block: RecyclerView.() -> Unit) {\n        onWrap = block\n    }\n\n    override fun onBindViewHolder(holder: QuickViewHolder, item: Unit?) = Unit\n\n    override fun onCreateViewHolder(\n        context: Context,\n        parent: ViewGroup,\n        viewType: Int,\n    ): QuickViewHolder {\n        return QuickViewHolder(R.layout.item_rv_wrapper, parent).also { viewHolder ->\n            val frame = viewHolder.itemView as ViewGroup\n            var rv = this@RvWrapper.customRv?.invoke(context)?.apply {\n                id = R.id.rv\n                isNestedScrollingEnabled = false\n            }\n            val prevRv = viewHolder.getView<RecyclerView>(R.id.rv)\n            if (rv != null) {\n                frame -= prevRv\n                rv.layoutParams = prevRv.layoutParams\n                frame += rv\n            } else {\n                rv = prevRv\n            }\n            rv.apply wr@{\n                this@wr.layoutManager = this@RvWrapper.layoutManager()\n                this@wr.adapter = this@RvWrapper.adapter\n                this@RvWrapper.wrapper = this@wr\n                onWrap?.invoke(this@wr)\n                outlineProvider = object : ViewOutlineProvider() {\n                    override fun getOutline(view: View, outline: Outline) {\n                        outline.setRoundRect(0, 0, view.width, view.height, 8f.dpToPx())\n                    }\n                }\n                clipToOutline = true\n            }\n        }\n    }\n}"
  },
  {
    "path": "app/src/main/java/com/yenaly/han1meviewer/ui/adapter/SharedHKeyframesRvAdapter.kt",
    "content": "package com.yenaly.han1meviewer.ui.adapter\n\nimport android.annotation.SuppressLint\nimport android.content.Context\nimport android.view.ViewGroup\nimport android.widget.TextView\nimport androidx.core.text.method.LinkMovementMethodCompat\nimport androidx.recyclerview.widget.DiffUtil\nimport androidx.recyclerview.widget.LinearLayoutManager\nimport androidx.recyclerview.widget.RecyclerView\nimport com.chad.library.adapter4.BaseDifferAdapter\nimport com.chad.library.adapter4.viewholder.QuickViewHolder\nimport com.itxca.spannablex.spannable\nimport com.yenaly.han1meviewer.R\nimport com.yenaly.han1meviewer.VIDEO_CODE\nimport com.yenaly.han1meviewer.logic.entity.HKeyframeEntity\nimport com.yenaly.han1meviewer.logic.entity.HKeyframeHeader\nimport com.yenaly.han1meviewer.logic.entity.HKeyframeType\nimport com.yenaly.han1meviewer.ui.activity.VideoActivity\nimport com.yenaly.yenaly_libs.utils.activity\nimport com.yenaly.yenaly_libs.utils.startActivity\n\n/**\n * @project Han1meViewer\n * @author Yenaly Liew\n * @time 2024/04/03 003 21:40\n */\nclass SharedHKeyframesRvAdapter : BaseDifferAdapter<HKeyframeType, QuickViewHolder>(COMPARATOR) {\n    init {\n        isStateViewEnable = true\n    }\n\n    companion object {\n        val COMPARATOR = object : DiffUtil.ItemCallback<HKeyframeType>() {\n            override fun areItemsTheSame(\n                oldItem: HKeyframeType,\n                newItem: HKeyframeType,\n            ) = when {\n                oldItem is HKeyframeEntity && newItem is HKeyframeEntity -> {\n                    oldItem.videoCode == newItem.videoCode\n                }\n\n                oldItem is HKeyframeHeader && newItem is HKeyframeHeader -> {\n                    oldItem.title == newItem.title\n                }\n\n                else -> false\n            }\n\n            @SuppressLint(\"DiffUtilEquals\")\n            override fun areContentsTheSame(\n                oldItem: HKeyframeType,\n                newItem: HKeyframeType,\n            ) = when {\n                oldItem is HKeyframeEntity && newItem is HKeyframeEntity -> {\n                    oldItem == newItem\n                }\n\n                oldItem is HKeyframeHeader && newItem is HKeyframeHeader -> {\n                    oldItem == newItem\n                }\n\n                else -> false\n\n            }\n        }\n    }\n\n    override fun getItemViewType(position: Int, list: List<HKeyframeType>): Int {\n        return list[position].itemType\n    }\n\n    override fun onBindViewHolder(holder: QuickViewHolder, position: Int, item: HKeyframeType?) {\n        when (getItemViewType(position)) {\n            HKeyframeType.H_KEYFRAME -> {\n                require(item is HKeyframeEntity)\n                holder.setText(R.id.tv_title, item.title)\n                holder.getView<TextView>(R.id.tv_video_code).apply {\n                    movementMethod = LinkMovementMethodCompat.getInstance()\n                    text = spannable {\n                        context.getString(R.string.h_keyframe_title_prefix).text()\n                        item.videoCode.span {\n                            clickable(color = context.getColor(R.color.video_code_link_text_color)) { _, videoCode ->\n                                context.activity?.startActivity<VideoActivity>(VIDEO_CODE to videoCode)\n                            }\n                            underline()\n                        }\n                    }\n                }\n                holder.getView<RecyclerView>(R.id.rv_h_keyframe).apply {\n                    layoutManager = LinearLayoutManager(context)\n                    adapter = HKeyframeRvAdapter(item.videoCode, item).apply {\n                        isLocal = item.author == null\n                        isShared = true\n                    }\n                }\n                holder.setText(R.id.tv_author, \"@${item.author}\")\n            }\n\n            HKeyframeType.HEADER -> {\n                require(item is HKeyframeHeader)\n                holder.setText(R.id.tv_title, item.title)\n            }\n        }\n    }\n\n    override fun onCreateViewHolder(\n        context: Context,\n        parent: ViewGroup,\n        viewType: Int,\n    ): QuickViewHolder {\n        return when (viewType) {\n            HKeyframeType.H_KEYFRAME -> {\n                QuickViewHolder(R.layout.item_shared_h_keyframes, parent)\n            }\n\n            HKeyframeType.HEADER -> {\n                QuickViewHolder(R.layout.layout_header_h_keyframes, parent)\n            }\n\n            else -> throw IllegalArgumentException(\"Unknown viewType: $viewType\")\n        }\n    }\n}"
  },
  {
    "path": "app/src/main/java/com/yenaly/han1meviewer/ui/adapter/SuperResolutionAdapter.kt",
    "content": "package com.yenaly.han1meviewer.ui.adapter\n\nimport android.R\nimport android.content.Context\nimport android.graphics.Color\nimport android.view.Gravity\nimport android.view.ViewGroup\nimport android.widget.TextView\nimport com.chad.library.adapter4.BaseQuickAdapter\nimport com.chad.library.adapter4.viewholder.QuickViewHolder\nimport com.yenaly.han1meviewer.ui.view.video.HJzvdStd\n\n/**\n * @project Han1meViewer\n * @author Yenaly Liew\n * @time 2023/11/26 026 16:09\n */\nclass SuperResolutionAdapter(private var currentIndex: Int) : BaseQuickAdapter<String, QuickViewHolder>(\n    HJzvdStd.superResolutionArray.toMutableList()\n) {\n\n    init {\n        isStateViewEnable = true\n    }\n\n    override fun onBindViewHolder(holder: QuickViewHolder, position: Int, item: String?) {\n        holder.setText(R.id.text1, item)\n        holder.setTextColor(\n            R.id.text1,\n            if (currentIndex == holder.bindingAdapterPosition) Color.parseColor(\"#fff85959\")\n            else Color.parseColor(\"#ffffff\")\n        )\n    }\n\n    override fun onCreateViewHolder(\n        context: Context,\n        parent: ViewGroup,\n        viewType: Int,\n    ): QuickViewHolder {\n        return QuickViewHolder(R.layout.simple_list_item_1, parent).also { viewHolder ->\n            viewHolder.getView<TextView>(R.id.text1).gravity = Gravity.CENTER\n        }\n    }\n}"
  },
  {
    "path": "app/src/main/java/com/yenaly/han1meviewer/ui/adapter/VideoColumnTitleAdapter.kt",
    "content": "package com.yenaly.han1meviewer.ui.adapter\n\nimport android.content.Context\nimport android.view.View\nimport android.view.ViewGroup\nimport android.widget.Button\nimport androidx.annotation.StringRes\nimport com.chad.library.adapter4.BaseSingleItemAdapter\nimport com.chad.library.adapter4.viewholder.QuickViewHolder\nimport com.yenaly.han1meviewer.EMPTY_STRING\nimport com.yenaly.han1meviewer.R\nimport com.yenaly.han1meviewer.ui.activity.MainActivity\nimport com.yenaly.yenaly_libs.utils.applicationContext\n\n/**\n * @project Han1meViewer\n * @author Yenaly Liew\n * @time 2023/11/27 027 12:55\n */\nclass VideoColumnTitleAdapter : BaseSingleItemAdapter<Unit, QuickViewHolder> {\n\n    private val notifyWhenSet: Boolean\n\n    var title: String = EMPTY_STRING\n        set(value) = if (notifyWhenSet) {\n            field = value\n            notifyItemChanged(0)\n        } else field = value\n\n    var subtitle: String? = null\n        set(value) = if (notifyWhenSet) {\n            field = value\n            notifyItemChanged(0)\n        } else field = value\n\n    var onMoreHanimeListener: ((View) -> Unit)? = null\n\n    constructor() : super() {\n        notifyWhenSet = true\n        title = EMPTY_STRING\n        subtitle = null\n    }\n\n    constructor(title: String, subtitle: String? = null, notifyWhenSet: Boolean = false) : super() {\n        this.title = title\n        this.subtitle = subtitle\n        this.notifyWhenSet = notifyWhenSet\n    }\n\n    constructor(\n        @StringRes title: Int,\n        @StringRes subtitle: Int = 0,\n        notifyWhenSet: Boolean = false,\n    ) : this(\n        applicationContext.getString(title),\n        if (subtitle != 0) applicationContext.getString(subtitle) else null,\n        notifyWhenSet\n    )\n\n    override fun onBindViewHolder(holder: QuickViewHolder, item: Unit?) {\n        holder.setGone(R.id.sub_title, subtitle == null)\n        holder.setText(R.id.title, title)\n        holder.setText(R.id.sub_title, subtitle)\n    }\n\n    override fun onCreateViewHolder(\n        context: Context,\n        parent: ViewGroup,\n        viewType: Int,\n    ): QuickViewHolder {\n        return QuickViewHolder(R.layout.item_video_column_title, parent).also { viewHolder ->\n            viewHolder.setGone(R.id.more, context !is MainActivity)\n            viewHolder.getView<Button>(R.id.more).setOnClickListener(onMoreHanimeListener)\n        }\n    }\n}"
  },
  {
    "path": "app/src/main/java/com/yenaly/han1meviewer/ui/adapter/VideoCommentRvAdapter.kt",
    "content": "package com.yenaly.han1meviewer.ui.adapter\n\nimport android.content.Context\nimport android.view.LayoutInflater\nimport android.view.ViewGroup\nimport android.widget.TextView\nimport androidx.core.text.parseAsHtml\nimport androidx.core.view.isVisible\nimport androidx.fragment.app.Fragment\nimport androidx.fragment.app.FragmentActivity\nimport androidx.recyclerview.widget.DiffUtil\nimport coil.load\nimport coil.transform.CircleCropTransformation\nimport com.chad.library.adapter4.BaseDifferAdapter\nimport com.chad.library.adapter4.viewholder.DataBindingHolder\nimport com.drake.spannable.replaceSpanFirst\nimport com.drake.spannable.span.HighlightSpan\nimport com.google.android.material.button.MaterialButton\nimport com.lxj.xpopup.XPopup\nimport com.yenaly.han1meviewer.COMMENT_ID\nimport com.yenaly.han1meviewer.Preferences\nimport com.yenaly.han1meviewer.R\nimport com.yenaly.han1meviewer.databinding.ItemVideoCommentBinding\nimport com.yenaly.han1meviewer.logic.model.VideoComments\nimport com.yenaly.han1meviewer.ui.fragment.video.ChildCommentPopupFragment\nimport com.yenaly.han1meviewer.ui.fragment.video.CommentFragment\nimport com.yenaly.han1meviewer.ui.popup.ReplyPopup\nimport com.yenaly.yenaly_libs.utils.makeBundle\nimport com.yenaly.yenaly_libs.utils.showShortToast\n\n/**\n * @project Han1meViewer\n * @author Yenaly Liew\n * @time 2023/11/26 026 16:19\n */\nclass VideoCommentRvAdapter(private val fragment: Fragment? = null) :\n    BaseDifferAdapter<VideoComments.VideoComment, DataBindingHolder<ItemVideoCommentBinding>>(\n        COMPARATOR\n    ) {\n\n    init {\n        isStateViewEnable = true\n    }\n\n    var replyPopup: ReplyPopup? = null\n\n    private var usernameRegex: Regex? = null\n\n    companion object {\n        private const val THUMB = 0\n\n        val COMPARATOR = object : DiffUtil.ItemCallback<VideoComments.VideoComment>() {\n            override fun areItemsTheSame(\n                oldItem: VideoComments.VideoComment,\n                newItem: VideoComments.VideoComment,\n            ): Boolean {\n                return oldItem.realReplyId == newItem.realReplyId\n            }\n\n            override fun areContentsTheSame(\n                oldItem: VideoComments.VideoComment,\n                newItem: VideoComments.VideoComment,\n            ): Boolean {\n                return oldItem == newItem\n            }\n\n            override fun getChangePayload(\n                oldItem: VideoComments.VideoComment,\n                newItem: VideoComments.VideoComment,\n            ): Any? {\n                return if (oldItem.post.likeCommentStatus != newItem.post.likeCommentStatus ||\n                    oldItem.post.unlikeCommentStatus != newItem.post.unlikeCommentStatus\n                ) THUMB else null\n            }\n        }\n    }\n\n    override fun submitList(list: List<VideoComments.VideoComment>?) {\n        super.submitList(list)\n        if (list !== items && fragment != null && fragment is ChildCommentPopupFragment) {\n            list?.map { it.username }?.toSet()?.let(::setUsernameRegex)\n        }\n    }\n\n    override fun onBindViewHolder(\n        holder: DataBindingHolder<ItemVideoCommentBinding>,\n        position: Int,\n        item: VideoComments.VideoComment?,\n    ) {\n        item ?: return\n\n        // 在release版中，主评论内容无法被复制，此为解决方法。\n        holder.binding.tvContent.fixTextSelection()\n\n        holder.binding.ivAvatar.load(item.avatar) {\n            crossfade(true)\n            transformations(CircleCropTransformation())\n        }\n        holder.binding.tvContent.text = kotlin.run {\n            val regex = usernameRegex\n            if (regex != null) {\n                item.content.replaceSpanFirst(regex) { _ ->\n                    HighlightSpan(context, R.color.at_person)\n                }\n            } else item.content\n        }\n        holder.binding.tvDate.text = item.date\n        holder.binding.tvUsername.text = item.username\n        holder.binding.btnViewMoreReplies.isVisible = item.hasMoreReplies\n        holder.binding.btnThumbUp.text = item.realLikesCount?.toString()\n        holder.binding.btnThumbUp.setThumbUpIcon(item.post.likeCommentStatus)\n        holder.binding.btnThumbDown.setThumbDownIcon(item.post.unlikeCommentStatus)\n    }\n\n    override fun onBindViewHolder(\n        holder: DataBindingHolder<ItemVideoCommentBinding>,\n        position: Int,\n        item: VideoComments.VideoComment?,\n        payloads: List<Any>,\n    ) {\n        if (payloads.isEmpty()) return super.onBindViewHolder(holder, position, item, payloads)\n        item ?: return\n        if (payloads.first() == THUMB) {\n            holder.binding.btnThumbUp.setThumbUpIcon(item.post.likeCommentStatus)\n            holder.binding.btnThumbDown.setThumbDownIcon(item.post.unlikeCommentStatus)\n            holder.binding.btnThumbUp.text = item.realLikesCount?.toString()\n        }\n    }\n\n    override fun onCreateViewHolder(\n        context: Context,\n        parent: ViewGroup,\n        viewType: Int,\n    ): DataBindingHolder<ItemVideoCommentBinding> {\n        return DataBindingHolder(\n            ItemVideoCommentBinding.inflate(\n                LayoutInflater.from(context), parent, false\n            )\n        ).also { viewHolder ->\n            viewHolder.binding.btnViewMoreReplies.setOnClickListener {\n                val position = viewHolder.bindingAdapterPosition\n                val item = getItem(position) ?: return@setOnClickListener\n                check(fragment != null && fragment is CommentFragment)\n                item.realReplyId.let { id ->\n                    ChildCommentPopupFragment().makeBundle(\n                        COMMENT_ID to id\n                    ).showIn(context as FragmentActivity)\n                }\n            }\n            viewHolder.binding.btnThumbUp.setOnClickListener {\n                if (!Preferences.isAlreadyLogin) {\n                    showShortToast(R.string.login_first)\n                    return@setOnClickListener\n                }\n                val position = viewHolder.bindingAdapterPosition\n                val item = getItem(position) ?: return@setOnClickListener\n\n                if (item.isChildComment) {\n                    check(fragment != null && fragment is ChildCommentPopupFragment)\n                    fragment.viewModel.likeChildComment(\n                        true, position, item,\n                        likeCommentStatus = item.post.likeCommentStatus\n                    )\n                } else {\n                    check(fragment != null && fragment is CommentFragment)\n                    fragment.viewModel.likeComment(\n                        true, position, item,\n                        likeCommentStatus = item.post.likeCommentStatus\n                    )\n                }\n            }\n            viewHolder.binding.btnThumbDown.setOnClickListener {\n                if (!Preferences.isAlreadyLogin) {\n                    showShortToast(R.string.login_first)\n                    return@setOnClickListener\n                }\n                val position = viewHolder.bindingAdapterPosition\n                val item = getItem(position) ?: return@setOnClickListener\n                if (item.isChildComment) {\n                    check(fragment != null && fragment is ChildCommentPopupFragment)\n                    fragment.viewModel.likeChildComment(\n                        false, position, item,\n                        unlikeCommentStatus = item.post.unlikeCommentStatus\n                    )\n                } else {\n                    check(fragment != null && fragment is CommentFragment)\n                    fragment.viewModel.likeComment(\n                        false, position, item,\n                        unlikeCommentStatus = item.post.unlikeCommentStatus\n                    )\n                }\n            }\n            viewHolder.binding.btnReply.setOnClickListener {\n                if (!Preferences.isAlreadyLogin) {\n                    showShortToast(R.string.login_first)\n                    return@setOnClickListener\n                }\n                val position = viewHolder.bindingAdapterPosition\n                val item = getItem(position) ?: return@setOnClickListener\n\n                ReplyPopup(context).also { commentPopup ->\n                    this.replyPopup = commentPopup\n                    if (item.isChildComment) {\n                        check(fragment != null && fragment is ChildCommentPopupFragment)\n                        fragment.apply {\n                            commentPopup.setOnSendListener {\n                                viewModel.postReply(\n                                    checkNotNull(commentId), commentPopup.comment\n                                )\n                            }\n                        }\n                        commentPopup.hint = context.getString(R.string.reply_child_comment)\n                        commentPopup.initCommentPrefix(item.username)\n                    } else {\n                        check(fragment != null && fragment is CommentFragment)\n                        fragment.apply {\n                            commentPopup.setOnSendListener {\n                                viewModel.postReply(\n                                    item.realReplyId,\n                                    commentPopup.comment\n                                )\n                            }\n                        }\n                        commentPopup.hint =\n                            \"\"\"${context.getString(R.string.reply)}<b>@${item.username}</b>\"\"\".parseAsHtml()\n                    }\n                    XPopup.Builder(context).autoOpenSoftInput(true).asCustom(commentPopup).show()\n                }\n            }\n        }\n    }\n\n    private fun MaterialButton.setThumbUpIcon(likeCommentStatus: Boolean) {\n        if (likeCommentStatus) {\n            setIconResource(R.drawable.ic_baseline_thumb_up_alt_24)\n        } else {\n            setIconResource(R.drawable.ic_baseline_thumb_up_off_alt_24)\n        }\n    }\n\n    private fun MaterialButton.setThumbDownIcon(unlikeCommentStatus: Boolean) {\n        if (unlikeCommentStatus) {\n            setIconResource(R.drawable.ic_baseline_thumb_down_alt_24)\n        } else {\n            setIconResource(R.drawable.ic_baseline_thumb_down_off_alt_24)\n        }\n    }\n\n    // stackoverflow-36801486\n    private fun TextView.fixTextSelection() {\n        setTextIsSelectable(false)\n        post { setTextIsSelectable(true) }\n    }\n\n    private fun setUsernameRegex(usernameList: Set<String>) {\n        usernameRegex = Regex(usernameList.joinToString(\"|\") { username ->\n            Regex.escape(\"@$username\")\n        })\n    }\n}"
  },
  {
    "path": "app/src/main/java/com/yenaly/han1meviewer/ui/adapter/VideoSpeedAdapter.kt",
    "content": "package com.yenaly.han1meviewer.ui.adapter\n\nimport android.content.Context\nimport android.graphics.Color\nimport android.view.Gravity\nimport android.view.ViewGroup\nimport android.widget.TextView\nimport com.chad.library.adapter4.BaseQuickAdapter\nimport com.chad.library.adapter4.viewholder.QuickViewHolder\nimport com.yenaly.han1meviewer.ui.view.video.HJzvdStd\n\n/**\n * @project Han1meViewer\n * @author Yenaly Liew\n * @time 2023/11/26 026 16:09\n */\nclass VideoSpeedAdapter(private var currentIndex: Int) : BaseQuickAdapter<String, QuickViewHolder>(\n    HJzvdStd.speedStringArray.toMutableList()\n) {\n\n    init {\n        isStateViewEnable = true\n    }\n\n    override fun onBindViewHolder(holder: QuickViewHolder, position: Int, item: String?) {\n        holder.setText(android.R.id.text1, item)\n        holder.setTextColor(\n            android.R.id.text1,\n            if (currentIndex == holder.bindingAdapterPosition) Color.parseColor(\"#fff85959\")\n            else Color.parseColor(\"#ffffff\")\n        )\n    }\n\n    override fun onCreateViewHolder(\n        context: Context,\n        parent: ViewGroup,\n        viewType: Int,\n    ): QuickViewHolder {\n        return QuickViewHolder(android.R.layout.simple_list_item_1, parent).also { viewHolder ->\n            viewHolder.getView<TextView>(android.R.id.text1).gravity = Gravity.CENTER\n        }\n    }\n}"
  },
  {
    "path": "app/src/main/java/com/yenaly/han1meviewer/ui/adapter/WatchHistoryRvAdapter.kt",
    "content": "package com.yenaly.han1meviewer.ui.adapter\n\nimport android.content.Context\nimport android.view.LayoutInflater\nimport android.view.ViewGroup\nimport androidx.recyclerview.widget.DiffUtil\nimport com.chad.library.adapter4.BaseDifferAdapter\nimport com.chad.library.adapter4.viewholder.DataBindingHolder\nimport com.yenaly.han1meviewer.LOCAL_DATE_TIME_FORMAT\nimport com.yenaly.han1meviewer.VIDEO_CODE\nimport com.yenaly.han1meviewer.databinding.ItemWatchHistoryBinding\nimport com.yenaly.han1meviewer.logic.entity.WatchHistoryEntity\nimport com.yenaly.han1meviewer.ui.activity.VideoActivity\nimport com.yenaly.han1meviewer.util.HImageMeower\nimport com.yenaly.han1meviewer.util.HImageMeower.loadUnhappily\nimport com.yenaly.yenaly_libs.utils.activity\nimport com.yenaly.yenaly_libs.utils.startActivity\nimport kotlinx.datetime.Instant\nimport kotlinx.datetime.TimeZone\nimport kotlinx.datetime.format\nimport kotlinx.datetime.toLocalDateTime\n\n/**\n * @project Han1meViewer\n * @author Yenaly Liew\n * @time 2023/11/26 026 15:35\n */\nclass WatchHistoryRvAdapter :\n    BaseDifferAdapter<WatchHistoryEntity, DataBindingHolder<ItemWatchHistoryBinding>>(COMPARATOR) {\n\n    init {\n        isStateViewEnable = true\n    }\n\n    companion object {\n        val COMPARATOR = object : DiffUtil.ItemCallback<WatchHistoryEntity>() {\n            override fun areItemsTheSame(\n                oldItem: WatchHistoryEntity,\n                newItem: WatchHistoryEntity,\n            ): Boolean {\n                return oldItem.id == newItem.id\n            }\n\n            override fun areContentsTheSame(\n                oldItem: WatchHistoryEntity,\n                newItem: WatchHistoryEntity,\n            ): Boolean {\n                return oldItem == newItem\n            }\n        }\n    }\n\n    override fun onBindViewHolder(\n        holder: DataBindingHolder<ItemWatchHistoryBinding>,\n        position: Int,\n        item: WatchHistoryEntity?,\n    ) {\n        item ?: return\n        holder.binding.ivCover.loadUnhappily(item.coverUrl, HImageMeower.placeholder(72, 128))\n        holder.binding.tvAddedTime.text =\n            Instant.fromEpochMilliseconds(item.watchDate).toLocalDateTime(\n                TimeZone.currentSystemDefault()\n            ).format(LOCAL_DATE_TIME_FORMAT)\n        // 不打算顯示發佈日期，所以不用設置\n        holder.binding.tvReleaseDate.text = null\n        holder.binding.tvTitle.text = item.title\n    }\n\n    override fun onCreateViewHolder(\n        context: Context,\n        parent: ViewGroup,\n        viewType: Int,\n    ): DataBindingHolder<ItemWatchHistoryBinding> {\n        return DataBindingHolder(\n            ItemWatchHistoryBinding.inflate(\n                LayoutInflater.from(context), parent, false\n            )\n        ).also { viewHolder ->\n            viewHolder.itemView.apply {\n                setOnClickListener {\n                    val position = viewHolder.bindingAdapterPosition\n                    val item = getItem(position) ?: return@setOnClickListener\n                    val videoCode = item.videoCode\n                    context.activity?.startActivity<VideoActivity>(VIDEO_CODE to videoCode)\n                }\n                // setOnLongClickListener 由各自的 Fragment 实现\n            }\n        }\n    }\n}"
  },
  {
    "path": "app/src/main/java/com/yenaly/han1meviewer/ui/fragment/IToolbarFragment.kt",
    "content": "package com.yenaly.han1meviewer.ui.fragment\n\nimport androidx.appcompat.app.AppCompatActivity\n\n/**\n * 用於 Fragment 要求 Activity 實現 Toolbar 的接口\n *\n * @project Han1meViewer\n * @author Yenaly Liew\n * @time 2023/08/27 027 16:29\n */\ninterface IToolbarFragment<T : AppCompatActivity> {\n\n    /**\n     * 設置 Toolbar\n     */\n    fun T.setupToolbar()\n\n}\n\n"
  },
  {
    "path": "app/src/main/java/com/yenaly/han1meviewer/ui/fragment/LoginNeededFragmentMixin.kt",
    "content": "package com.yenaly.han1meviewer.ui.fragment\n\nimport androidx.fragment.app.Fragment\nimport androidx.navigation.fragment.findNavController\nimport com.yenaly.han1meviewer.Preferences.isAlreadyLogin\n\n/**\n * 用於需要登錄的 Fragment\n *\n * @project Han1meViewer\n * @author Yenaly Liew\n * @time 2023/08/29 029 15:32\n */\n@JvmDefaultWithoutCompatibility\ninterface LoginNeededFragmentMixin {\n    /**\n     * 檢查是否已經登錄，如果沒有則返回上一個 Fragment\n     */\n    fun Fragment.checkLogin() {\n        if (!isAlreadyLogin) {\n            findNavController().navigateUp()\n        }\n    }\n}"
  },
  {
    "path": "app/src/main/java/com/yenaly/han1meviewer/ui/fragment/home/DownloadFragment.kt",
    "content": "package com.yenaly.han1meviewer.ui.fragment.home\n\nimport android.os.Bundle\nimport android.view.LayoutInflater\nimport android.view.ViewGroup\nimport androidx.fragment.app.activityViewModels\nimport com.yenaly.han1meviewer.R\nimport com.yenaly.han1meviewer.databinding.FragmentTabViewPagerOnlyBinding\nimport com.yenaly.han1meviewer.ui.activity.MainActivity\nimport com.yenaly.han1meviewer.ui.fragment.IToolbarFragment\nimport com.yenaly.han1meviewer.ui.fragment.home.download.DownloadedFragment\nimport com.yenaly.han1meviewer.ui.fragment.home.download.DownloadingFragment\nimport com.yenaly.han1meviewer.ui.viewmodel.DownloadViewModel\nimport com.yenaly.yenaly_libs.base.YenalyFragment\nimport com.yenaly.yenaly_libs.utils.view.attach\nimport com.yenaly.yenaly_libs.utils.view.setUpFragmentStateAdapter\n\n/**\n * 下载影片总Fragment，暫時由[DownloadedFragment]全權托管\n *\n * @project Han1meViewer\n * @author Yenaly Liew\n * @time 2022/08/01 001 17:44\n */\nclass DownloadFragment : YenalyFragment<FragmentTabViewPagerOnlyBinding>(),\n    IToolbarFragment<MainActivity> {\n\n    val viewModel by activityViewModels<DownloadViewModel>()\n\n    private val tabNameArray = intArrayOf(R.string.downloading, R.string.downloaded)\n\n    override fun getViewBinding(\n        inflater: LayoutInflater,\n        container: ViewGroup?\n    ): FragmentTabViewPagerOnlyBinding {\n        return FragmentTabViewPagerOnlyBinding.inflate(inflater, container, false)\n    }\n\n    override fun initData(savedInstanceState: Bundle?) {\n        (activity as MainActivity).setupToolbar()\n        initViewPager()\n    }\n\n    private fun initViewPager() {\n\n        binding.viewPager.setUpFragmentStateAdapter(this) {\n            addFragment { DownloadingFragment() }\n            addFragment { DownloadedFragment() }\n        }\n\n        binding.tabLayout.attach(binding.viewPager) { tab, position ->\n            tab.setText(tabNameArray[position])\n        }\n    }\n\n    override fun MainActivity.setupToolbar() {\n        val toolbar = this@DownloadFragment.binding.toolbar\n        setSupportActionBar(toolbar)\n        supportActionBar!!.setSubtitle(R.string.download)\n        toolbar.setupWithMainNavController()\n    }\n}"
  },
  {
    "path": "app/src/main/java/com/yenaly/han1meviewer/ui/fragment/home/HomePageFragment.kt",
    "content": "package com.yenaly.han1meviewer.ui.fragment.home\n\nimport android.annotation.SuppressLint\nimport android.content.res.ColorStateList\nimport android.content.res.Configuration\nimport android.graphics.Color\nimport android.graphics.RenderEffect\nimport android.graphics.Shader\nimport android.graphics.drawable.ColorDrawable\nimport android.graphics.drawable.GradientDrawable\nimport android.os.Build\nimport android.os.Bundle\nimport android.view.LayoutInflater\nimport android.view.ViewGroup\nimport androidx.annotation.RequiresApi\nimport androidx.core.graphics.drawable.toBitmapOrNull\nimport androidx.core.view.ViewCompat\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.interpolator.view.animation.FastOutSlowInInterpolator\nimport androidx.lifecycle.Lifecycle\nimport androidx.lifecycle.lifecycleScope\nimport androidx.lifecycle.repeatOnLifecycle\nimport androidx.navigation.ui.onNavDestinationSelected\nimport androidx.palette.graphics.Palette\nimport androidx.recyclerview.widget.ConcatAdapter\nimport androidx.recyclerview.widget.GridLayoutManager\nimport coil.load\nimport com.yenaly.han1meviewer.ADVANCED_SEARCH_MAP\nimport com.yenaly.han1meviewer.AdvancedSearchMap\nimport com.yenaly.han1meviewer.HAdvancedSearch\nimport com.yenaly.han1meviewer.R\nimport com.yenaly.han1meviewer.VIDEO_CODE\nimport com.yenaly.han1meviewer.advancedSearchMapOf\nimport com.yenaly.han1meviewer.databinding.FragmentHomePageBinding\nimport com.yenaly.han1meviewer.logic.model.HomePage\nimport com.yenaly.han1meviewer.logic.state.WebsiteState\nimport com.yenaly.han1meviewer.ui.StateLayoutMixin\nimport com.yenaly.han1meviewer.ui.activity.MainActivity\nimport com.yenaly.han1meviewer.ui.activity.PreviewActivity\nimport com.yenaly.han1meviewer.ui.activity.SearchActivity\nimport com.yenaly.han1meviewer.ui.activity.VideoActivity\nimport com.yenaly.han1meviewer.ui.adapter.HRvItemAdapter.Companion.withTitleSection\nimport com.yenaly.han1meviewer.ui.adapter.HanimeVideoRvAdapter\nimport com.yenaly.han1meviewer.ui.fragment.IToolbarFragment\nimport com.yenaly.han1meviewer.ui.viewmodel.MainViewModel\nimport com.yenaly.han1meviewer.util.GridSpacingDecoration\nimport com.yenaly.han1meviewer.util.addUpdateListener\nimport com.yenaly.han1meviewer.util.colorTransition\nimport com.yenaly.han1meviewer.util.dpToPx\nimport com.yenaly.yenaly_libs.base.YenalyFragment\nimport com.yenaly.yenaly_libs.utils.startActivity\nimport kotlinx.coroutines.launch\n\n/**\n * @project Hanime1\n * @author Yenaly Liew\n * @time 2022/06/12 012 12:31\n */\nclass HomePageFragment : YenalyFragment<FragmentHomePageBinding>(),\n    IToolbarFragment<MainActivity>, StateLayoutMixin {\n\n    companion object {\n        private val animInterpolator = FastOutSlowInInterpolator()\n        private const val ANIM_DURATION = 300L\n    }\n\n    val viewModel by activityViewModels<MainViewModel>()\n\n    private val latestHanimeAdapter = HanimeVideoRvAdapter()\n    private val latestReleaseAdapter = HanimeVideoRvAdapter()\n    private val latestUploadAdapter = HanimeVideoRvAdapter()\n    private val chineseSubtitleAdapter = HanimeVideoRvAdapter()\n    private val hanimeTheyWatchedAdapter = HanimeVideoRvAdapter()\n    private val hanimeCurrentAdapter = HanimeVideoRvAdapter()\n    private val hotHanimeMonthlyAdapter = HanimeVideoRvAdapter()\n\n    private val concatAdapter = ConcatAdapter(\n        latestHanimeAdapter.withTitleSection(R.string.latest_hanime) {\n            toSearchActivity(advancedSearchMapOf(HAdvancedSearch.GENRE to \"裏番\"))\n        },\n        latestReleaseAdapter.withTitleSection(R.string.latest_release) {\n            toSearchActivity(advancedSearchMapOf(HAdvancedSearch.SORT to \"最新上市\"))\n        },\n        latestUploadAdapter.withTitleSection(R.string.latest_upload) {\n            toSearchActivity(advancedSearchMapOf(HAdvancedSearch.SORT to \"最新上傳\"))\n        },\n        chineseSubtitleAdapter.withTitleSection(R.string.chinese_subtitle) {\n            toSearchActivity(\n                advancedSearchMapOf(\n                    HAdvancedSearch.TAGS to hashMapOf<Int, Any>(R.string.video_attr to \"中文字幕\"),\n                    HAdvancedSearch.SORT to \"最新上傳\"\n                )\n            )\n        },\n        hanimeTheyWatchedAdapter.withTitleSection(R.string.they_watched) {\n            toSearchActivity(advancedSearchMapOf(HAdvancedSearch.SORT to \"他們在看\"))\n        },\n        hanimeCurrentAdapter.withTitleSection(R.string.ranking_today) {\n            toSearchActivity(advancedSearchMapOf(HAdvancedSearch.SORT to \"本日排行\"))\n        },\n        hotHanimeMonthlyAdapter.withTitleSection(R.string.ranking_this_month) {\n            toSearchActivity(advancedSearchMapOf(HAdvancedSearch.SORT to \"本月排行\"))\n        }\n    )\n\n    /**\n     * 用於判斷是否需要 setExpanded，防止重複喚出 AppBar\n     */\n    private var isAfterRefreshing = false\n\n    override fun getViewBinding(\n        inflater: LayoutInflater,\n        container: ViewGroup?\n    ): FragmentHomePageBinding {\n        return FragmentHomePageBinding.inflate(inflater, container, false)\n    }\n\n    /**\n     * 初始化数据\n     */\n    override fun initData(savedInstanceState: Bundle?) {\n\n        (activity as MainActivity).setupToolbar()\n        binding.state.init()\n\n        if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.S) {\n            easterEgg()\n        }\n\n        binding.rv.adapter = concatAdapter\n        binding.rv.clipToPadding = false\n        if (binding.rv.layoutManager is GridLayoutManager) {\n            (binding.rv.layoutManager as GridLayoutManager).spanSizeLookup = object : GridLayoutManager.SpanSizeLookup() {\n                override fun getSpanSize(position: Int): Int {\n                    return if (position == 0) 2 else 1\n                }\n            }\n            binding.rv.addItemDecoration(\n                GridSpacingDecoration(\n                    spanCount = 2,\n                    spacing = 16.dpToPx(),\n                    start = 1,\n                    includeEdge = false\n                )\n            )\n        }\n\n\n        ViewCompat.setOnApplyWindowInsetsListener(binding.rv) { v, insets ->\n            val systemBars = insets.getInsets(WindowInsetsCompat.Type.navigationBars())\n            v.updatePadding(bottom = systemBars.bottom)\n            WindowInsetsCompat.CONSUMED\n        }\n        binding.homePageSrl.apply {\n            setOnRefreshListener {\n                isAfterRefreshing = false\n                // will enter here firstly. cuz the flow's def value is Loading.\n                viewModel.getHomePage()\n            }\n            setEnableLoadMore(false)\n        }\n    }\n\n    @SuppressLint(\"SetTextI18n\")\n    override fun bindDataObservers() {\n        viewLifecycleOwner.lifecycleScope.launch {\n            repeatOnLifecycle(Lifecycle.State.CREATED) {\n                viewModel.homePageFlow.collect { state ->\n                    binding.rv.isGone = state !is WebsiteState.Success\n                    binding.banner.isVisible =\n                        state is WebsiteState.Success || binding.banner.isVisible // 只有在刚开始的时候是不可见的\n                    if (!isAfterRefreshing) {\n                        binding.appBar.setExpanded(state is WebsiteState.Success, true)\n                    }\n                    when (state) {\n                        is WebsiteState.Loading -> {\n                            binding.homePageSrl.autoRefresh()\n                            binding.rv.isGone = latestHanimeAdapter.items.isEmpty()\n                        }\n\n                        is WebsiteState.Success -> {\n                            isAfterRefreshing = true\n                            binding.homePageSrl.finishRefresh()\n                            initBanner(state.info)\n                            latestHanimeAdapter.submitList(state.info.latestHanime)\n                            latestUploadAdapter.submitList(state.info.latestUpload)\n                            hotHanimeMonthlyAdapter.submitList(state.info.hotHanimeMonthly)\n                            hanimeCurrentAdapter.submitList(state.info.hanimeCurrent)\n                            hanimeTheyWatchedAdapter.submitList(state.info.hanimeTheyWatched)\n                            latestReleaseAdapter.submitList(state.info.latestRelease)\n                            chineseSubtitleAdapter.submitList(state.info.chineseSubtitle)\n                            binding.state.showContent()\n                        }\n\n                        is WebsiteState.Error -> {\n                            binding.homePageSrl.finishRefresh()\n                            binding.state.showError(state.throwable)\n                        }\n                    }\n                }\n            }\n        }\n    }\n\n    override fun onDestroyView() {\n        binding.rv.adapter = null\n        super.onDestroyView()\n    }\n\n    override fun onConfigurationChanged(newConfig: Configuration) {\n        super.onConfigurationChanged(newConfig)\n    }\n\n    private fun initBanner(info: HomePage) {\n        info.banner?.let { banner ->\n            binding.tvBannerTitle.text = banner.title\n            binding.tvBannerDesc.text = banner.description\n            binding.cover.load(banner.picUrl) {\n                crossfade(true)\n                allowHardware(false)\n                target(\n                    onStart = binding.cover::setImageDrawable,\n                    onError = binding.cover::setImageDrawable,\n                    onSuccess = {\n                        binding.cover.setImageDrawable(it)\n                        it.toBitmapOrNull()?.let(Palette::Builder)?.generate { p ->\n                            p?.let(::handlePalette)\n                        }\n                    }\n                )\n            }\n            binding.btnBanner.isEnabled = banner.videoCode != null\n            binding.btnBanner.setOnClickListener {\n                banner.videoCode?.let { videoCode ->\n                    requireActivity().startActivity<VideoActivity>(VIDEO_CODE to videoCode)\n                }\n            }\n        }\n    }\n\n    // #issue-160: 修复字段销毁后调用引发的错误\n    private fun handlePalette(p: Palette) {\n        bindingOrNull?.let { binding ->\n            val lightVibrant = p.getLightVibrantColor(Color.RED)\n\n            val buttonBgColor =\n                p.darkVibrantSwatch?.rgb ?: p.darkMutedSwatch?.rgb ?: Color.TRANSPARENT\n            val darkVibrantForContentScrim =\n                p.darkVibrantSwatch?.rgb ?: p.darkMutedSwatch?.rgb ?: p.lightVibrantSwatch?.rgb\n                ?: p.lightMutedSwatch?.rgb ?: Color.BLACK\n            binding.collapsingToolbar.setContentScrimColor(darkVibrantForContentScrim)\n            binding.btnBanner.background = GradientDrawable().apply {\n                colors = intArrayOf(Color.TRANSPARENT, buttonBgColor)\n                orientation = GradientDrawable.Orientation.LEFT_RIGHT\n            }\n            binding.ivBanner.backgroundTintList = ColorStateList.valueOf(Color.WHITE)\n            colorTransition(\n                fromColor = (binding.aColor.background as ColorDrawable).color,\n                toColor = lightVibrant\n            ) {\n                interpolator = animInterpolator\n                duration = ANIM_DURATION\n                addUpdateListener(this@HomePageFragment) {\n                    val color = it.animatedValue as Int\n                    binding.aColor.setBackgroundColor(color)\n                }\n            }\n        }\n    }\n\n    private fun toSearchActivity(advancedSearchMap: AdvancedSearchMap) {\n        startActivity<SearchActivity>(ADVANCED_SEARCH_MAP to advancedSearchMap)\n    }\n\n    @RequiresApi(Build.VERSION_CODES.S)\n    private var easterEggCount = 1f\n\n    @RequiresApi(Build.VERSION_CODES.S)\n    private fun easterEgg() {\n        binding.cover.setOnClickListener {\n            binding.cover.setRenderEffect(\n                RenderEffect.createBlurEffect(\n                    easterEggCount,\n                    easterEggCount,\n                    Shader.TileMode.CLAMP\n                )\n            )\n            easterEggCount++\n        }\n        binding.cover.setOnLongClickListener {\n            binding.cover.setRenderEffect(null)\n            easterEggCount = 1f\n            true\n        }\n    }\n\n    override fun MainActivity.setupToolbar() {\n        val toolbar = this@HomePageFragment.binding.toolbar\n        setSupportActionBar(toolbar)\n        this@HomePageFragment.addMenu(R.menu.menu_main_toolbar, viewLifecycleOwner) { item ->\n            when (item.itemId) {\n                R.id.tb_search -> {\n                    startActivity<SearchActivity>()\n                    return@addMenu true\n                }\n\n                R.id.tb_previews -> {\n                    startActivity<PreviewActivity>()\n                    return@addMenu true\n                }\n            }\n            return@addMenu item.onNavDestinationSelected(navController)\n        }\n\n        toolbar.setupWithMainNavController()\n    }\n}"
  },
  {
    "path": "app/src/main/java/com/yenaly/han1meviewer/ui/fragment/home/MyFavVideoFragment.kt",
    "content": "package com.yenaly.han1meviewer.ui.fragment.home\n\nimport android.annotation.SuppressLint\nimport android.content.res.Configuration\nimport android.os.Bundle\nimport android.view.LayoutInflater\nimport android.view.ViewGroup\nimport androidx.fragment.app.activityViewModels\nimport androidx.lifecycle.Lifecycle\nimport androidx.lifecycle.lifecycleScope\nimport androidx.lifecycle.repeatOnLifecycle\nimport androidx.recyclerview.widget.GridLayoutManager\nimport com.yenaly.han1meviewer.R\nimport com.yenaly.han1meviewer.VideoCoverSize\nimport com.yenaly.han1meviewer.databinding.FragmentPageListBinding\nimport com.yenaly.han1meviewer.logic.state.PageLoadingState\nimport com.yenaly.han1meviewer.logic.state.WebsiteState\nimport com.yenaly.han1meviewer.ui.StateLayoutMixin\nimport com.yenaly.han1meviewer.ui.activity.MainActivity\nimport com.yenaly.han1meviewer.ui.adapter.HanimeMyListVideoAdapter\nimport com.yenaly.han1meviewer.ui.fragment.IToolbarFragment\nimport com.yenaly.han1meviewer.ui.fragment.LoginNeededFragmentMixin\nimport com.yenaly.han1meviewer.ui.viewmodel.MyListViewModel\nimport com.yenaly.han1meviewer.util.showAlertDialog\nimport com.yenaly.yenaly_libs.base.YenalyFragment\nimport com.yenaly.yenaly_libs.utils.showShortToast\nimport com.yenaly.yenaly_libs.utils.unsafeLazy\nimport kotlinx.coroutines.flow.collectLatest\nimport kotlinx.coroutines.launch\n\n/**\n * @project Han1meViewer\n * @author Yenaly Liew\n * @time 2022/07/04 004 22:43\n */\nclass MyFavVideoFragment : YenalyFragment<FragmentPageListBinding>(),\n    IToolbarFragment<MainActivity>, LoginNeededFragmentMixin, StateLayoutMixin {\n\n    val viewModel by activityViewModels<MyListViewModel>()\n\n    private var page: Int\n        set(value) {\n            viewModel.fav.favVideoPage = value\n        }\n        get() = viewModel.fav.favVideoPage\n\n    private val adapter by unsafeLazy { HanimeMyListVideoAdapter() }\n\n    override fun getViewBinding(\n        inflater: LayoutInflater,\n        container: ViewGroup?\n    ): FragmentPageListBinding {\n        return FragmentPageListBinding.inflate(inflater, container, false)\n    }\n\n    override fun initData(savedInstanceState: Bundle?) {\n        checkLogin()\n        (activity as MainActivity).setupToolbar()\n\n        binding.state.init()\n\n        adapter.setOnItemLongClickListener { _, _, position ->\n            val item = adapter.getItem(position) ?: return@setOnItemLongClickListener true\n            requireContext().showAlertDialog {\n                setTitle(R.string.delete_fav)\n                setMessage(getString(R.string.sure_to_delete_s, item.title))\n                setPositiveButton(R.string.confirm) { _, _ ->\n                    viewModel.fav.deleteMyFavVideo(item.videoCode, position)\n                }\n                setNegativeButton(R.string.cancel, null)\n            }\n            return@setOnItemLongClickListener true\n        }\n\n        binding.rvPageList.apply {\n            layoutManager = GridLayoutManager(context, VideoCoverSize.Simplified.videoInOneLine)\n            adapter = this@MyFavVideoFragment.adapter\n        }\n\n        binding.srlPageList.apply {\n            setOnLoadMoreListener {\n                getMyFavVideo()\n            }\n            setOnRefreshListener {\n                getNewMyFavVideo()\n            }\n            setDisableContentWhenRefresh(true)\n        }\n    }\n\n    @SuppressLint(\"SetTextI18n\")\n    override fun bindDataObservers() {\n        viewLifecycleOwner.lifecycleScope.launch {\n            repeatOnLifecycle(Lifecycle.State.STARTED) {\n                viewModel.fav.favVideoStateFlow.collect { state ->\n                    when (state) {\n                        is PageLoadingState.Error -> {\n                            binding.srlPageList.finishRefresh()\n                            binding.srlPageList.finishLoadMore(false)\n                            // set error view\n                            binding.state.showError(state.throwable)\n                        }\n\n                        is PageLoadingState.Loading -> {\n                            adapter.stateView = null\n                            if (viewModel.fav.favVideoFlow.value.isEmpty()) binding.srlPageList.autoRefresh()\n                        }\n\n                        is PageLoadingState.NoMoreData -> {\n                            binding.srlPageList.finishLoadMoreWithNoMoreData()\n                            if (viewModel.fav.favVideoFlow.value.isEmpty()) binding.state.showEmpty()\n                        }\n\n                        is PageLoadingState.Success -> {\n                            page++\n                            binding.srlPageList.finishRefresh()\n                            binding.srlPageList.finishLoadMore(true)\n                            binding.state.showContent()\n                        }\n                    }\n                }\n            }\n        }\n\n        viewLifecycleOwner.lifecycleScope.launch {\n            repeatOnLifecycle(Lifecycle.State.STARTED) {\n                viewModel.fav.favVideoFlow.collectLatest {\n                    adapter.submitList(it)\n                }\n            }\n        }\n\n        viewLifecycleOwner.lifecycleScope.launch {\n            viewModel.fav.deleteMyFavVideoFlow.collect { state ->\n                when (state) {\n                    is WebsiteState.Error -> {\n                        showShortToast(R.string.delete_failed)\n                        state.throwable.printStackTrace()\n                    }\n\n                    is WebsiteState.Loading -> {\n                    }\n\n                    is WebsiteState.Success -> {\n                        showShortToast(R.string.delete_success)\n                    }\n                }\n            }\n        }\n    }\n\n    override fun onConfigurationChanged(newConfig: Configuration) {\n        super.onConfigurationChanged(newConfig)\n        binding.rvPageList.layoutManager =\n            GridLayoutManager(context, VideoCoverSize.Simplified.videoInOneLine)\n    }\n\n    private fun getMyFavVideo() {\n        viewModel.fav.getMyFavVideoItems(page)\n    }\n\n    private fun getNewMyFavVideo() {\n        page = 1\n        viewModel.fav.clearMyListItems()\n        getMyFavVideo()\n    }\n\n    override fun MainActivity.setupToolbar() {\n        val toolbar = this@MyFavVideoFragment.binding.toolbar\n        setSupportActionBar(toolbar)\n        supportActionBar!!.setSubtitle(R.string.fav_video)\n        this@MyFavVideoFragment.addMenu(\n            R.menu.menu_my_list_toolbar,\n            viewLifecycleOwner\n        ) { menuItem ->\n            when (menuItem.itemId) {\n                R.id.tb_help -> {\n                    requireContext().showAlertDialog {\n                        setTitle(R.string.attention)\n                        setMessage(R.string.long_press_to_cancel_fav)\n                        setPositiveButton(R.string.ok, null)\n                    }\n                    return@addMenu true\n                }\n            }\n            return@addMenu false\n        }\n\n        toolbar.setupWithMainNavController()\n    }\n}"
  },
  {
    "path": "app/src/main/java/com/yenaly/han1meviewer/ui/fragment/home/MyPlaylistFragment.kt",
    "content": "package com.yenaly.han1meviewer.ui.fragment.home\n\nimport android.annotation.SuppressLint\nimport android.content.res.Configuration\nimport android.os.Bundle\nimport android.view.LayoutInflater\nimport android.view.View\nimport android.view.ViewGroup\nimport android.widget.EditText\nimport android.widget.TextView\nimport androidx.core.view.GravityCompat\nimport androidx.core.view.isVisible\nimport androidx.fragment.app.activityViewModels\nimport androidx.lifecycle.Lifecycle\nimport androidx.lifecycle.lifecycleScope\nimport androidx.lifecycle.repeatOnLifecycle\nimport androidx.recyclerview.widget.GridLayoutManager\nimport androidx.recyclerview.widget.LinearLayoutManager\nimport coil.load\nimport com.yenaly.han1meviewer.R\nimport com.yenaly.han1meviewer.VideoCoverSize\nimport com.yenaly.han1meviewer.databinding.FragmentPlaylistBinding\nimport com.yenaly.han1meviewer.logic.state.PageLoadingState\nimport com.yenaly.han1meviewer.logic.state.WebsiteState\nimport com.yenaly.han1meviewer.ui.StateLayoutMixin\nimport com.yenaly.han1meviewer.ui.activity.MainActivity\nimport com.yenaly.han1meviewer.ui.adapter.HanimeMyListVideoAdapter\nimport com.yenaly.han1meviewer.ui.adapter.PlaylistRvAdapter\nimport com.yenaly.han1meviewer.ui.fragment.IToolbarFragment\nimport com.yenaly.han1meviewer.ui.fragment.LoginNeededFragmentMixin\nimport com.yenaly.han1meviewer.ui.viewmodel.MyListViewModel\nimport com.yenaly.han1meviewer.util.showAlertDialog\nimport com.yenaly.yenaly_libs.base.YenalyFragment\nimport com.yenaly.yenaly_libs.utils.showShortToast\nimport com.yenaly.yenaly_libs.utils.unsafeLazy\nimport com.yenaly.yenaly_libs.utils.view.clickTrigger\nimport kotlinx.coroutines.flow.collectLatest\nimport kotlinx.coroutines.launch\n\n/**\n * @project Han1meViewer\n * @author Yenaly Liew\n * @time 2022/07/04 004 22:43\n */\nclass MyPlaylistFragment : YenalyFragment<FragmentPlaylistBinding>(),\n    IToolbarFragment<MainActivity>, LoginNeededFragmentMixin, StateLayoutMixin {\n\n    val viewModel by activityViewModels<MyListViewModel>()\n\n    private var page: Int\n        set(value) {\n            viewModel.playlist.playlistPage = value\n        }\n        get() = viewModel.playlist.playlistPage\n\n    var listCode: String?\n        set(value) {\n            viewModel.playlist.playlistCode = value\n            binding.playlistHeader.isVisible = value != null\n        }\n        get() = viewModel.playlist.playlistCode\n\n    var listTitle: String?\n        set(value) {\n            viewModel.playlist.playlistTitle = value\n            binding.playlistHeader.title = value\n        }\n        get() = viewModel.playlist.playlistTitle\n\n    private var listDesc: String?\n        set(value) {\n            viewModel.playlist.playlistDesc = value\n            binding.playlistHeader.description = value\n        }\n        get() = viewModel.playlist.playlistDesc\n\n    private val adapter by unsafeLazy { HanimeMyListVideoAdapter() }\n    private val playlistsAdapter by unsafeLazy { PlaylistRvAdapter(this) }\n\n    /**\n     * 用於判斷是否需要 setExpanded，防止重複喚出 AppBar\n     */\n    private var isAfterRefreshing = false\n\n    override fun getViewBinding(\n        inflater: LayoutInflater,\n        container: ViewGroup?\n    ): FragmentPlaylistBinding {\n        return FragmentPlaylistBinding.inflate(inflater, container, false)\n    }\n\n    override fun initData(savedInstanceState: Bundle?) {\n        checkLogin()\n        (activity as MainActivity).setupToolbar()\n        binding.statePlaylist.init {\n            loadingLayout = R.layout.layout_empty_view\n            onLoading {\n                findViewById<TextView>(R.id.tv_empty).setText(R.string.loading)\n            }\n        }\n        binding.statePageList.init {\n            loadingLayout = R.layout.layout_empty_view\n            onLoading {\n                findViewById<TextView>(R.id.tv_empty).setText(R.string.slide_to_choose_list)\n            }\n        }\n\n        binding.rvPlaylist.apply {\n            layoutManager = LinearLayoutManager(context)\n            adapter = playlistsAdapter\n        }\n\n        binding.rvPageList.apply {\n            layoutManager = GridLayoutManager(context, VideoCoverSize.Simplified.videoInOneLine)\n            adapter = this@MyPlaylistFragment.adapter\n        }\n\n        initPlaylistHeader()\n\n        viewModel.playlist.getPlaylists()\n\n        adapter.setOnItemLongClickListener { _, _, position ->\n            val item = adapter.getItem(position) ?: return@setOnItemLongClickListener true\n            requireContext().showAlertDialog {\n                setTitle(R.string.delete_playlist)\n                setMessage(getString(R.string.sure_to_delete_s, item.title))\n                setPositiveButton(R.string.confirm) { _, _ ->\n                    listCode?.let { listCode ->\n                        viewModel.playlist.deleteFromPlaylist(listCode, item.videoCode, position)\n                    }\n                }\n                setNegativeButton(R.string.cancel, null)\n            }\n            return@setOnItemLongClickListener true\n        }\n\n        binding.btnRefreshPlaylists.clickTrigger(viewLifecycleOwner.lifecycle) {\n            viewModel.playlist.getPlaylists()\n        }\n        binding.btnNewPlaylist.setOnClickListener {\n            requireContext().showAlertDialog {\n                setTitle(R.string.create_new_playlist)\n                val etView = View.inflate(context, R.layout.dialog_playlist_modify_edit_text, null)\n                val etTitle = etView.findViewById<EditText>(R.id.et_title)\n                val etDesc = etView.findViewById<EditText>(R.id.et_desc)\n                setView(etView)\n                setPositiveButton(R.string.confirm) { _, _ ->\n                    viewModel.playlist.createPlaylist(\n                        etTitle.text.toString(),\n                        etDesc.text.toString()\n                    )\n                }\n                setNegativeButton(R.string.cancel, null)\n\n            }\n        }\n\n        binding.srlPageList.apply {\n            setOnLoadMoreListener {\n                getPlaylistItems()\n            }\n            setOnRefreshListener {\n                getNewPlaylistItems()\n            }\n            setDisableContentWhenRefresh(true)\n        }\n    }\n\n    override fun onStart() {\n        super.onStart()\n        binding.playlistHeader.isVisible = listCode != null\n        binding.playlistHeader.title = listTitle\n        binding.playlistHeader.description = listDesc\n    }\n\n    @SuppressLint(\"SetTextI18n\", \"NotifyDataSetChanged\")\n    override fun bindDataObservers() {\n        viewLifecycleOwner.lifecycleScope.launch {\n            repeatOnLifecycle(Lifecycle.State.STARTED) {\n                viewModel.playlist.playlistStateFlow.collect { state ->\n                    val isExist =\n                        state is PageLoadingState.Success || state is PageLoadingState.NoMoreData\n                    if (!isAfterRefreshing) {\n                        binding.appBar.setExpanded(isExist, true)\n                        binding.playlistHeader.isVisible =\n                            isExist || binding.playlistHeader.isVisible // 只有在刚开始的时候是不可见的\n                    }\n                    when (state) {\n                        is PageLoadingState.Error -> {\n                            binding.srlPageList.finishRefresh()\n                            binding.srlPageList.finishLoadMore(false)\n                            // set error view\n                            binding.statePageList.showError(state.throwable)\n                        }\n\n                        is PageLoadingState.Loading -> {\n                            adapter.stateView = null\n                            if (listCode == null) {\n                                binding.statePageList.showLoading()\n                            } else if (viewModel.playlist.playlistFlow.value.isEmpty()) {\n                                binding.srlPageList.autoRefresh()\n                            }\n                        }\n\n                        is PageLoadingState.NoMoreData -> {\n                            binding.srlPageList.finishLoadMoreWithNoMoreData()\n                            binding.srlPageList.finishRefresh()\n                            if (viewModel.playlist.playlistFlow.value.isEmpty()) {\n                                adapter.notifyDataSetChanged() // 這裡要用notifyDataSetChanged()，不然不會出現空白頁，而且crash\n                                binding.statePageList.showEmpty()\n                            }\n                        }\n\n                        is PageLoadingState.Success -> {\n                            page++\n                            binding.srlPageList.finishRefresh()\n                            binding.srlPageList.finishLoadMore(true)\n                            if (!isAfterRefreshing) {\n                                binding.cover.load(state.info.hanimeInfo.firstOrNull()?.coverUrl) {\n                                    crossfade(true)\n                                }\n                            }\n                            isAfterRefreshing = true\n                            listDesc = state.info.desc\n                            binding.statePageList.showContent()\n                        }\n                    }\n                }\n            }\n        }\n\n        viewLifecycleOwner.lifecycleScope.launch {\n            repeatOnLifecycle(Lifecycle.State.STARTED) {\n                viewModel.playlist.playlistFlow.collectLatest { list ->\n                    if (listCode != null) adapter.submitList(list)\n                }\n            }\n        }\n\n        viewLifecycleOwner.lifecycleScope.launch {\n            repeatOnLifecycle(Lifecycle.State.STARTED) {\n                viewModel.playlist.playlistsFlow.collect { state ->\n                    when (state) {\n                        is WebsiteState.Success -> {\n                            playlistsAdapter.submitList(state.info.playlists)\n                            if (state.info.playlists.isEmpty()) {\n                                binding.statePlaylist.showEmpty()\n                            } else {\n                                binding.statePlaylist.showContent()\n                            }\n                        }\n\n                        is WebsiteState.Error -> {\n                            binding.statePlaylist.showError()\n                        }\n\n                        is WebsiteState.Loading -> {\n                            binding.statePlaylist.showLoading()\n                        }\n                    }\n                }\n            }\n        }\n\n        viewLifecycleOwner.lifecycleScope.launch {\n            viewModel.playlist.deleteFromPlaylistFlow.collect { state ->\n                when (state) {\n                    is WebsiteState.Error -> {\n                        showShortToast(R.string.delete_failed)\n                    }\n\n                    is WebsiteState.Loading -> {\n                    }\n\n                    is WebsiteState.Success -> {\n                        showShortToast(R.string.delete_success)\n                    }\n                }\n            }\n        }\n\n        viewLifecycleOwner.lifecycleScope.launch {\n            viewModel.playlist.modifyPlaylistFlow.collect { state ->\n                when (state) {\n                    is WebsiteState.Error -> {\n                        showShortToast(R.string.modify_failed)\n                    }\n\n                    is WebsiteState.Loading -> {\n                    }\n\n                    is WebsiteState.Success -> {\n                        showShortToast(R.string.modify_success)\n                        if (state.info.isDeleted) {\n                            listCode = null\n                            listTitle = null\n                            listDesc = null\n                            binding.appBar.setExpanded(false, true)\n                            binding.statePageList.showLoading()\n                        } else {\n                            listTitle = state.info.title\n                            listDesc = state.info.desc\n                        }\n                        viewModel.playlist.getPlaylists()\n                    }\n                }\n            }\n        }\n\n        viewLifecycleOwner.lifecycleScope.launch {\n            viewModel.playlist.createPlaylistFlow.collect { state ->\n                when (state) {\n                    is WebsiteState.Error -> {\n                        showShortToast(R.string.add_failed)\n                    }\n\n                    is WebsiteState.Loading -> Unit\n                    is WebsiteState.Success -> {\n                        showShortToast(R.string.add_success)\n                        viewModel.playlist.getPlaylists()\n                    }\n                }\n            }\n        }\n    }\n\n    private fun getPlaylistItems() {\n        val listCode = listCode\n        if (listCode != null) {\n            viewModel.playlist.getPlaylistItems(page, listCode = listCode)\n        } else {\n            binding.statePageList.showLoading()\n        }\n    }\n\n    fun getNewPlaylistItems() {\n        page = 1\n        isAfterRefreshing = false\n        viewModel.playlist.clearMyListItems()\n        getPlaylistItems()\n    }\n\n    override fun onConfigurationChanged(newConfig: Configuration) {\n        super.onConfigurationChanged(newConfig)\n        binding.rvPageList.layoutManager =\n            GridLayoutManager(context, VideoCoverSize.Simplified.videoInOneLine)\n    }\n\n    override fun MainActivity.setupToolbar() {\n        val toolbar = this@MyPlaylistFragment.binding.toolbar\n        val dlPlaylist = this@MyPlaylistFragment.binding.dlPlaylist\n        setSupportActionBar(toolbar)\n        supportActionBar!!.setSubtitle(R.string.play_list)\n        this@MyPlaylistFragment.addMenu(\n            R.menu.menu_playlist_toolbar, viewLifecycleOwner\n        ) { menuItem ->\n            when (menuItem.itemId) {\n                R.id.tb_open_drawer -> {\n                    dlPlaylist.openDrawer(GravityCompat.END)\n                    return@addMenu true\n                }\n            }\n            return@addMenu false\n        }\n\n        toolbar.setupWithMainNavController()\n    }\n\n    private fun initPlaylistHeader() {\n        binding.appBar.setExpanded(false)\n        binding.playlistHeader.onChangedListener = { title, desc ->\n            listCode?.let { listCode ->\n                viewModel.playlist.modifyPlaylist(listCode, title, desc, delete = false)\n            }\n        }\n        binding.playlistHeader.onDeleteActionListener = {\n            listCode?.let { listCode ->\n                viewModel.playlist.modifyPlaylist(\n                    listCode, listTitle.orEmpty(), listDesc.orEmpty(), delete = true\n                )\n            }\n        }\n    }\n}"
  },
  {
    "path": "app/src/main/java/com/yenaly/han1meviewer/ui/fragment/home/MyWatchLaterFragment.kt",
    "content": "package com.yenaly.han1meviewer.ui.fragment.home\n\nimport android.annotation.SuppressLint\nimport android.content.res.Configuration\nimport android.os.Bundle\nimport android.view.LayoutInflater\nimport android.view.ViewGroup\nimport androidx.fragment.app.activityViewModels\nimport androidx.lifecycle.Lifecycle\nimport androidx.lifecycle.lifecycleScope\nimport androidx.lifecycle.repeatOnLifecycle\nimport androidx.recyclerview.widget.GridLayoutManager\nimport com.yenaly.han1meviewer.R\nimport com.yenaly.han1meviewer.VideoCoverSize\nimport com.yenaly.han1meviewer.databinding.FragmentPageListBinding\nimport com.yenaly.han1meviewer.logic.state.PageLoadingState\nimport com.yenaly.han1meviewer.logic.state.WebsiteState\nimport com.yenaly.han1meviewer.ui.StateLayoutMixin\nimport com.yenaly.han1meviewer.ui.activity.MainActivity\nimport com.yenaly.han1meviewer.ui.adapter.HanimeMyListVideoAdapter\nimport com.yenaly.han1meviewer.ui.fragment.IToolbarFragment\nimport com.yenaly.han1meviewer.ui.fragment.LoginNeededFragmentMixin\nimport com.yenaly.han1meviewer.ui.viewmodel.MyListViewModel\nimport com.yenaly.han1meviewer.util.showAlertDialog\nimport com.yenaly.yenaly_libs.base.YenalyFragment\nimport com.yenaly.yenaly_libs.utils.showShortToast\nimport com.yenaly.yenaly_libs.utils.unsafeLazy\nimport kotlinx.coroutines.flow.collectLatest\nimport kotlinx.coroutines.launch\n\n/**\n * @project Han1meViewer\n * @author Yenaly Liew\n * @time 2022/07/04 004 22:42\n */\nclass MyWatchLaterFragment : YenalyFragment<FragmentPageListBinding>(),\n    IToolbarFragment<MainActivity>, LoginNeededFragmentMixin, StateLayoutMixin {\n\n    val viewModel by activityViewModels<MyListViewModel>()\n\n    private var page: Int\n        set(value) {\n            viewModel.watchLater.watchLaterPage = value\n        }\n        get() = viewModel.watchLater.watchLaterPage\n\n    private val adapter by unsafeLazy { HanimeMyListVideoAdapter() }\n\n    override fun getViewBinding(\n        inflater: LayoutInflater,\n        container: ViewGroup?\n    ): FragmentPageListBinding {\n        return FragmentPageListBinding.inflate(inflater, container, false)\n    }\n\n    override fun initData(savedInstanceState: Bundle?) {\n        checkLogin()\n        (activity as MainActivity).setupToolbar()\n        binding.state.init()\n\n        adapter.setOnItemLongClickListener { _, _, position ->\n            val item = adapter.getItem(position) ?: return@setOnItemLongClickListener true\n            requireContext().showAlertDialog {\n                setTitle(R.string.delete_watch_later)\n                setMessage(getString(R.string.sure_to_delete_s, item.title))\n                setPositiveButton(R.string.confirm) { _, _ ->\n                    viewModel.watchLater.deleteMyWatchLater(item.videoCode, position)\n                }\n                setNegativeButton(R.string.cancel, null)\n            }\n            return@setOnItemLongClickListener true\n        }\n\n        binding.rvPageList.apply {\n            layoutManager = GridLayoutManager(context, VideoCoverSize.Simplified.videoInOneLine)\n            adapter = this@MyWatchLaterFragment.adapter\n        }\n\n        binding.srlPageList.apply {\n            setOnLoadMoreListener {\n                getMyWatchLater()\n            }\n            setOnRefreshListener {\n                getNewMyWatchLater()\n            }\n            setDisableContentWhenRefresh(true)\n        }\n    }\n\n    @SuppressLint(\"SetTextI18n\")\n    override fun bindDataObservers() {\n        viewLifecycleOwner.lifecycleScope.launch {\n            repeatOnLifecycle(Lifecycle.State.STARTED) {\n                viewModel.watchLater.watchLaterStateFlow.collect { state ->\n                    when (state) {\n                        is PageLoadingState.Error -> {\n                            binding.srlPageList.finishRefresh()\n                            binding.srlPageList.finishLoadMore(false)\n                            // set error view\n                            binding.state.showError()\n                        }\n\n                        is PageLoadingState.Loading -> {\n                            adapter.stateView = null\n                            if (viewModel.watchLater.watchLaterFlow.value.isEmpty()) binding.srlPageList.autoRefresh()\n                        }\n\n                        is PageLoadingState.NoMoreData -> {\n                            binding.srlPageList.finishLoadMoreWithNoMoreData()\n                            if (viewModel.watchLater.watchLaterFlow.value.isEmpty()) binding.state.showEmpty()\n                        }\n\n                        is PageLoadingState.Success -> {\n                            page++\n                            binding.srlPageList.finishRefresh()\n                            binding.srlPageList.finishLoadMore(true)\n                            binding.state.showContent()\n                        }\n                    }\n                }\n            }\n        }\n\n        viewLifecycleOwner.lifecycleScope.launch {\n            repeatOnLifecycle(Lifecycle.State.STARTED) {\n                viewModel.watchLater.watchLaterFlow.collectLatest {\n                    adapter.submitList(it)\n                }\n            }\n        }\n\n        viewLifecycleOwner.lifecycleScope.launch {\n            viewModel.watchLater.deleteMyWatchLaterFlow.collect { state ->\n                when (state) {\n                    is WebsiteState.Error -> {\n                        showShortToast(R.string.delete_failed)\n                        state.throwable.printStackTrace()\n                    }\n\n                    is WebsiteState.Loading -> {\n                    }\n\n                    is WebsiteState.Success -> {\n                        showShortToast(R.string.delete_success)\n                    }\n                }\n            }\n        }\n    }\n\n    override fun onConfigurationChanged(newConfig: Configuration) {\n        super.onConfigurationChanged(newConfig)\n        binding.rvPageList.layoutManager =\n            GridLayoutManager(context, VideoCoverSize.Simplified.videoInOneLine)\n    }\n\n    private fun getMyWatchLater() {\n        viewModel.watchLater.getMyWatchLaterItems(page)\n    }\n\n    private fun getNewMyWatchLater() {\n        page = 1\n        viewModel.watchLater.clearMyListItems()\n        getMyWatchLater()\n    }\n\n    override fun MainActivity.setupToolbar() {\n        val toolbar = this@MyWatchLaterFragment.binding.toolbar\n        setSupportActionBar(toolbar)\n        supportActionBar!!.setSubtitle(R.string.watch_later)\n        this@MyWatchLaterFragment.addMenu(\n            R.menu.menu_my_list_toolbar,\n            viewLifecycleOwner\n        ) { menuItem ->\n            when (menuItem.itemId) {\n                R.id.tb_help -> {\n                    requireContext().showAlertDialog {\n                        setTitle(R.string.attention)\n                        setMessage(R.string.long_press_to_cancel_watch_later)\n                        setPositiveButton(R.string.ok, null)\n                    }\n                    return@addMenu true\n                }\n            }\n            return@addMenu false\n        }\n\n        toolbar.setupWithMainNavController()\n    }\n}"
  },
  {
    "path": "app/src/main/java/com/yenaly/han1meviewer/ui/fragment/home/WatchHistoryFragment.kt",
    "content": "package com.yenaly.han1meviewer.ui.fragment.home\n\nimport android.os.Bundle\nimport android.view.LayoutInflater\nimport android.view.ViewGroup\nimport androidx.fragment.app.activityViewModels\nimport androidx.lifecycle.flowWithLifecycle\nimport androidx.lifecycle.lifecycleScope\nimport androidx.recyclerview.widget.LinearLayoutManager\nimport com.yenaly.han1meviewer.R\nimport com.yenaly.han1meviewer.databinding.FragmentPageListBinding\nimport com.yenaly.han1meviewer.ui.StateLayoutMixin\nimport com.yenaly.han1meviewer.ui.activity.MainActivity\nimport com.yenaly.han1meviewer.ui.adapter.WatchHistoryRvAdapter\nimport com.yenaly.han1meviewer.ui.fragment.IToolbarFragment\nimport com.yenaly.han1meviewer.ui.viewmodel.MainViewModel\nimport com.yenaly.han1meviewer.util.setStateViewLayout\nimport com.yenaly.han1meviewer.util.showAlertDialog\nimport com.yenaly.yenaly_libs.base.YenalyFragment\nimport com.yenaly.yenaly_libs.utils.unsafeLazy\nimport kotlinx.coroutines.launch\n\n/**\n * @project Han1meViewer\n * @author Yenaly Liew\n * @time 2022/07/01 001 21:23\n */\nclass WatchHistoryFragment : YenalyFragment<FragmentPageListBinding>(),\n    IToolbarFragment<MainActivity>, StateLayoutMixin {\n\n    val viewModel by activityViewModels<MainViewModel>()\n\n    private val historyAdapter by unsafeLazy { WatchHistoryRvAdapter() }\n\n    override fun getViewBinding(\n        inflater: LayoutInflater,\n        container: ViewGroup?\n    ): FragmentPageListBinding {\n        return FragmentPageListBinding.inflate(inflater, container, false)\n    }\n\n    override fun initData(savedInstanceState: Bundle?) {\n        (activity as MainActivity).setupToolbar()\n\n        binding.rvPageList.apply {\n            layoutManager = LinearLayoutManager(context)\n            adapter = historyAdapter\n        }\n        binding.srlPageList.finishRefreshWithNoMoreData()\n        historyAdapter.setStateViewLayout(R.layout.layout_empty_view)\n        historyAdapter.setOnItemLongClickListener { _, _, position ->\n            val data = historyAdapter.getItem(position) ?: return@setOnItemLongClickListener true\n            requireContext().showAlertDialog {\n                setTitle(R.string.delete_history)\n                setMessage(getString(R.string.sure_to_delete_s, data.title))\n                setPositiveButton(R.string.confirm) { _, _ ->\n                    viewModel.deleteWatchHistory(data)\n                }\n                setNegativeButton(R.string.cancel, null)\n            }\n            return@setOnItemLongClickListener true\n        }\n    }\n\n    override fun bindDataObservers() {\n        viewLifecycleOwner.lifecycleScope.launch {\n            viewModel.loadAllWatchHistories()\n                .flowWithLifecycle(viewLifecycleOwner.lifecycle)\n                .collect {\n                    historyAdapter.submitList(it)\n                }\n        }\n    }\n\n    override fun MainActivity.setupToolbar() {\n        val toolbar = this@WatchHistoryFragment.binding.toolbar\n        setSupportActionBar(toolbar)\n        supportActionBar!!.setSubtitle(R.string.watch_history)\n        this@WatchHistoryFragment.addMenu(\n            R.menu.menu_watch_history_toolbar,\n            viewLifecycleOwner\n        ) { item ->\n            when (item.itemId) {\n                R.id.tb_delete -> {\n                    requireContext().showAlertDialog {\n                        setTitle(R.string.sure_to_delete)\n                        setMessage(R.string.sure_to_delete_all_histories)\n                        setPositiveButton(R.string.sure) { _, _ ->\n                            viewModel.deleteAllWatchHistories()\n                            historyAdapter.submitList(null)\n                        }\n                        setNegativeButton(R.string.no, null)\n                    }\n                    return@addMenu true\n                }\n\n                R.id.tb_help -> {\n                    requireContext().showAlertDialog {\n                        setTitle(R.string.attention)\n                        setMessage(R.string.long_press_to_delete_all_histories)\n                        setPositiveButton(R.string.ok, null)\n                    }\n                    return@addMenu true\n                }\n            }\n            return@addMenu false\n        }\n\n        toolbar.setupWithMainNavController()\n    }\n}"
  },
  {
    "path": "app/src/main/java/com/yenaly/han1meviewer/ui/fragment/home/download/DownloadedFragment.kt",
    "content": "package com.yenaly.han1meviewer.ui.fragment.home.download\n\nimport android.os.Bundle\nimport android.view.LayoutInflater\nimport android.view.Menu\nimport android.view.MenuInflater\nimport android.view.MenuItem\nimport android.view.ViewGroup\nimport androidx.appcompat.app.AppCompatActivity\nimport androidx.core.view.MenuProvider\nimport androidx.core.view.ViewCompat\nimport androidx.core.view.WindowInsetsCompat\nimport androidx.core.view.updatePadding\nimport androidx.fragment.app.activityViewModels\nimport androidx.lifecycle.Lifecycle\nimport androidx.lifecycle.flowWithLifecycle\nimport androidx.lifecycle.lifecycleScope\nimport androidx.recyclerview.widget.LinearLayoutManager\nimport com.yenaly.han1meviewer.R\nimport com.yenaly.han1meviewer.databinding.FragmentListOnlyBinding\nimport com.yenaly.han1meviewer.logic.entity.download.HanimeDownloadEntity\nimport com.yenaly.han1meviewer.ui.StateLayoutMixin\nimport com.yenaly.han1meviewer.ui.activity.MainActivity\nimport com.yenaly.han1meviewer.ui.adapter.HanimeDownloadedRvAdapter\nimport com.yenaly.han1meviewer.ui.fragment.IToolbarFragment\nimport com.yenaly.han1meviewer.ui.viewmodel.DownloadViewModel\nimport com.yenaly.han1meviewer.util.setStateViewLayout\nimport com.yenaly.yenaly_libs.base.YenalyFragment\nimport com.yenaly.yenaly_libs.utils.activity\nimport com.yenaly.yenaly_libs.utils.unsafeLazy\nimport kotlinx.coroutines.launch\n\n/**\n * 已下载影片\n *\n * @project Han1meViewer\n * @author Yenaly Liew\n * @time 2022/08/01 001 17:45\n */\nclass DownloadedFragment : YenalyFragment<FragmentListOnlyBinding>(),\n    IToolbarFragment<MainActivity>, StateLayoutMixin {\n\n    val viewModel by activityViewModels<DownloadViewModel>()\n\n    private val adapter by unsafeLazy { HanimeDownloadedRvAdapter(this) }\n\n    override fun getViewBinding(\n        inflater: LayoutInflater,\n        container: ViewGroup?\n    ): FragmentListOnlyBinding {\n        return FragmentListOnlyBinding.inflate(inflater, container, false)\n    }\n\n    override fun initData(savedInstanceState: Bundle?) {\n        binding.rvList.layoutManager = LinearLayoutManager(context)\n        binding.rvList.adapter = adapter\n        ViewCompat.setOnApplyWindowInsetsListener(binding.rvList) { v, insets ->\n            val navBar = insets.getInsets(WindowInsetsCompat.Type.navigationBars())\n            v.updatePadding(bottom = navBar.bottom)\n            WindowInsetsCompat.CONSUMED\n        }\n        adapter.setStateViewLayout(R.layout.layout_empty_view)\n        loadAllSortedDownloadedHanime()\n    }\n\n    override fun bindDataObservers() {\n        viewLifecycleOwner.lifecycleScope.launch {\n            viewModel.downloaded.flowWithLifecycle(viewLifecycleOwner.lifecycle)\n                .collect {\n                    adapter.submitList(it)\n                }\n        }\n    }\n\n    override fun MainActivity.setupToolbar() {\n        val fv = this@DownloadedFragment.viewModel\n        addMenuProvider(object : MenuProvider {\n            override fun onCreateMenu(menu: Menu, menuInflater: MenuInflater) {\n                menu.clear()\n                menuInflater.inflate(R.menu.menu_downloaded_toolbar, menu)\n                menu.findItem(fv.currentSortOptionId)?.isChecked = true\n            }\n\n            override fun onMenuItemSelected(menuItem: MenuItem): Boolean {\n                fv.currentSortOptionId = menuItem.itemId\n                menuItem.isChecked = true\n                return loadAllSortedDownloadedHanime()\n            }\n        }, viewLifecycleOwner, Lifecycle.State.RESUMED)\n    }\n\n    override fun onResume() {\n        super.onResume()\n        activity<AppCompatActivity>().supportActionBar?.setSubtitle(R.string.downloaded)\n    }\n\n    // #issue-18: 添加下载区排序\n    private fun loadAllSortedDownloadedHanime(): Boolean = when (viewModel.currentSortOptionId) {\n        R.id.sm_sort_by_alphabet_ascending -> {\n            viewModel.loadAllDownloadedHanime(\n                sortedBy = HanimeDownloadEntity.SortedBy.TITLE,\n                ascending = true\n            )\n            true\n        }\n\n        R.id.sm_sort_by_alphabet_descending -> {\n            viewModel.loadAllDownloadedHanime(\n                sortedBy = HanimeDownloadEntity.SortedBy.TITLE,\n                ascending = false\n            )\n            true\n        }\n\n        R.id.sm_sort_by_date_ascending -> {\n            viewModel.loadAllDownloadedHanime(\n                sortedBy = HanimeDownloadEntity.SortedBy.ID,\n                ascending = true\n            )\n            true\n        }\n\n        R.id.sm_sort_by_date_descending -> {\n            viewModel.loadAllDownloadedHanime(\n                sortedBy = HanimeDownloadEntity.SortedBy.ID,\n                ascending = false\n            )\n            true\n        }\n\n        else -> false\n    }\n}"
  },
  {
    "path": "app/src/main/java/com/yenaly/han1meviewer/ui/fragment/home/download/DownloadingFragment.kt",
    "content": "package com.yenaly.han1meviewer.ui.fragment.home.download\n\nimport android.os.Bundle\nimport android.util.Log\nimport android.view.LayoutInflater\nimport android.view.ViewGroup\nimport androidx.appcompat.app.AppCompatActivity\nimport androidx.core.view.ViewCompat\nimport androidx.core.view.WindowInsetsCompat\nimport androidx.core.view.updatePadding\nimport androidx.fragment.app.activityViewModels\nimport androidx.lifecycle.Lifecycle\nimport androidx.lifecycle.flowWithLifecycle\nimport androidx.lifecycle.lifecycleScope\nimport androidx.recyclerview.widget.ConcatAdapter\nimport androidx.recyclerview.widget.LinearLayoutManager\nimport com.yenaly.han1meviewer.R\nimport com.yenaly.han1meviewer.databinding.FragmentListOnlyBinding\nimport com.yenaly.han1meviewer.ui.StateLayoutMixin\nimport com.yenaly.han1meviewer.ui.activity.MainActivity\nimport com.yenaly.han1meviewer.ui.adapter.HanimeDownloadingRvAdapter\nimport com.yenaly.han1meviewer.ui.adapter.HanimeUpdateRvAdapter\nimport com.yenaly.han1meviewer.ui.fragment.IToolbarFragment\nimport com.yenaly.han1meviewer.ui.viewmodel.DownloadViewModel\nimport com.yenaly.han1meviewer.util.HImageMeower\nimport com.yenaly.han1meviewer.util.requestPostNotificationPermission\nimport com.yenaly.han1meviewer.util.setStateViewLayout\nimport com.yenaly.han1meviewer.worker.HanimeDownloadManagerV2\nimport com.yenaly.han1meviewer.worker.HanimeDownloadWorker\nimport com.yenaly.yenaly_libs.base.YenalyFragment\nimport com.yenaly.yenaly_libs.utils.activity\nimport com.yenaly.yenaly_libs.utils.unsafeLazy\nimport kotlinx.coroutines.Dispatchers\nimport kotlinx.coroutines.flow.map\nimport kotlinx.coroutines.flow.merge\nimport kotlinx.coroutines.launch\nimport java.util.UUID\n\n/**\n * 正在下载的影片\n *\n * @project Han1meViewer\n * @author Yenaly Liew\n * @time 2022/08/01 001 17:45\n */\nclass DownloadingFragment : YenalyFragment<FragmentListOnlyBinding>(),\n    IToolbarFragment<MainActivity>, StateLayoutMixin {\n\n    val viewModel by activityViewModels<DownloadViewModel>()\n\n    private val hanimeDownloadingRvAdapter by unsafeLazy { HanimeDownloadingRvAdapter(this) }\n    private val hanimeUpdatingRvAdapter by unsafeLazy { HanimeUpdateRvAdapter(this) }\n\n    private val concatAdapter by unsafeLazy {\n        ConcatAdapter(\n        hanimeDownloadingRvAdapter,\n            hanimeUpdatingRvAdapter\n        )\n    }\n\n    override fun getViewBinding(\n        inflater: LayoutInflater,\n        container: ViewGroup?\n    ): FragmentListOnlyBinding {\n        return FragmentListOnlyBinding.inflate(inflater, container, false)\n    }\n\n    override fun initData(savedInstanceState: Bundle?) {\n        binding.rvList.layoutManager = LinearLayoutManager(context)\n        binding.rvList.adapter = hanimeDownloadingRvAdapter\n        ViewCompat.setOnApplyWindowInsetsListener(binding.rvList) { v, insets ->\n            val navBar = insets.getInsets(WindowInsetsCompat.Type.navigationBars())\n            v.updatePadding(bottom = navBar.bottom)\n            WindowInsetsCompat.CONSUMED\n        }\n        hanimeDownloadingRvAdapter.setStateViewLayout(R.layout.layout_empty_view)\n        // binding.rvList.itemAnimator?.changeDuration = 0\n    }\n\n    override fun bindDataObservers() {\n        viewLifecycleOwner.lifecycleScope.launch {\n            merge(\n                viewModel.loadAllDownloadingHanime().map { it to null },\n                viewModel.loadUpdating().map { null to it }\n            ).flowWithLifecycle(viewLifecycleOwner.lifecycle)\n                .collect { (downloading, updating) ->\n                    downloading?.let { hanimeDownloadingRvAdapter.submitList(it) }\n                    updating?.let { hanimeUpdatingRvAdapter.submitList(listOf(it)) }\n                }\n        }\n    }\n\n    private suspend fun test() {\n        requireContext().requestPostNotificationPermission()\n        val uuid = UUID.randomUUID()\n        val videoCode = uuid.toString()\n        val args = HanimeDownloadWorker.Args(\n            quality = \"720P\",\n            downloadUrl = \"https://ash-speed.hetzner.com/100MB.bin\",\n            videoType = \"mp4\",\n            hanimeName = \"Test-$uuid\",\n            videoCode = videoCode,\n            coverUrl = HImageMeower.placeholder(100, 200),\n        )\n        // HanimeDownloadManager.addTask(args, redownload = false)\n        HanimeDownloadManagerV2.addTask(args, redownload = false)\n    }\n\n    // #issue-44: 一键返回主页提议\n    override fun MainActivity.setupToolbar() {\n        this@DownloadingFragment.addMenu(\n            R.menu.menu_downloading_toolbar,\n            viewLifecycleOwner,\n            Lifecycle.State.RESUMED\n        ) {\n            when (it.itemId) {\n                R.id.tb_test -> {\n                    viewLifecycleOwner.lifecycleScope.launch(Dispatchers.Default) {\n                        test()\n                    }\n                    return@addMenu true\n                }\n\n                R.id.tb_start_all -> {\n                    hanimeDownloadingRvAdapter.items.forEachIndexed { index, entity ->\n                        if (!entity.isDownloading) {\n                            HanimeDownloadManagerV2.resumeTask(entity)\n//                            adapter.continueWork(entity.copy(isDownloading = true))\n                            hanimeDownloadingRvAdapter.notifyItemChanged(index)\n                        }\n                    }\n                    return@addMenu true\n                }\n\n                R.id.tb_pause_all -> {\n                    hanimeDownloadingRvAdapter.items.forEachIndexed { index, entity ->\n                        if (entity.isDownloading) {\n                            HanimeDownloadManagerV2.stopTask(entity)\n//                            with(adapter) {\n//                                cancelUniqueWorkAndPause(entity.copy(isDownloading = false))\n//                            }\n                            hanimeDownloadingRvAdapter.notifyItemChanged(index)\n                        }\n                    }\n                    return@addMenu true\n\n                }\n            }\n            return@addMenu false\n        }\n    }\n\n    override fun onResume() {\n        super.onResume()\n        activity<AppCompatActivity>().supportActionBar?.setSubtitle(R.string.downloading)\n    }\n}"
  },
  {
    "path": "app/src/main/java/com/yenaly/han1meviewer/ui/fragment/search/HCheckBoxFragment.kt",
    "content": "package com.yenaly.han1meviewer.ui.fragment.search\n\nimport android.os.Bundle\nimport android.view.LayoutInflater\nimport android.view.View\nimport android.view.ViewGroup\nimport androidx.recyclerview.widget.GridLayoutManager\nimport androidx.recyclerview.widget.RecyclerView\nimport com.yenaly.han1meviewer.R\nimport com.yenaly.han1meviewer.logic.model.SearchOption\nimport com.yenaly.han1meviewer.ui.fragment.search.HMultiChoicesDialog.Companion.UNKNOWN_ADAPTER\nimport com.yenaly.yenaly_libs.base.frame.FrameFragment\nimport com.yenaly.yenaly_libs.utils.arguments\nimport com.yenaly.yenaly_libs.utils.dp\nimport com.yenaly.yenaly_libs.utils.makeBundle\nimport com.yenaly.yenaly_libs.utils.unsafeLazy\n\nclass HCheckBoxFragment : FrameFragment() {\n\n    companion object {\n\n        const val SCOPE_NAME_RES = \"scope_name_res\"\n        const val ITEMS = \"items\"\n        const val SPAN_COUNT = \"span_count\"\n\n        const val DEF_SPAN_COUNT = 3\n\n        fun newInstance(\n            scopeNameRes: Int,\n            items: List<SearchOption>,\n            spanCount: Int = DEF_SPAN_COUNT,\n        ) = HCheckBoxFragment().makeBundle(\n            SCOPE_NAME_RES to scopeNameRes,\n            ITEMS to items,\n            SPAN_COUNT to spanCount\n        )\n    }\n\n    val scopeNameRes by arguments(SCOPE_NAME_RES, UNKNOWN_ADAPTER)\n    val items by arguments(ITEMS, emptyList<SearchOption>())\n    val spanCount by arguments(SPAN_COUNT, DEF_SPAN_COUNT)\n\n    val adapter by unsafeLazy {\n        HMultiChoicesDialog.adapterMap?.get(scopeNameRes)\n    }\n\n    override fun onCreateView(\n        inflater: LayoutInflater,\n        container: ViewGroup?,\n        savedInstanceState: Bundle?\n    ): View {\n        val rv = inflater.inflate(R.layout.layout_rv_scrollbars, container, false) as RecyclerView\n        rv.apply {\n            layoutParams = ViewGroup.LayoutParams(\n                ViewGroup.LayoutParams.MATCH_PARENT,\n                ViewGroup.LayoutParams.MATCH_PARENT\n            )\n\n            isVerticalScrollBarEnabled = true\n\n            setPadding(8.dp, 0, 8.dp, 0)\n\n            layoutManager = GridLayoutManager(context, spanCount)\n            adapter = this@HCheckBoxFragment.adapter\n        }\n        return rv\n    }\n\n    override fun onViewCreated(view: View, savedInstanceState: Bundle?) {\n        super.onViewCreated(view, savedInstanceState)\n        this@HCheckBoxFragment.adapter?.submitList(items)\n    }\n}"
  },
  {
    "path": "app/src/main/java/com/yenaly/han1meviewer/ui/fragment/search/HMultiChoicesDialog.kt",
    "content": "package com.yenaly.han1meviewer.ui.fragment.search\n\nimport android.annotation.SuppressLint\nimport android.content.Context\nimport android.content.DialogInterface\nimport android.util.SparseArray\nimport android.view.View\nimport androidx.annotation.StringRes\nimport androidx.appcompat.app.AlertDialog\nimport androidx.core.util.size\nimport androidx.core.util.valueIterator\nimport androidx.core.view.isVisible\nimport androidx.viewpager2.widget.ViewPager2\nimport com.google.android.material.tabs.TabLayout\nimport com.yenaly.han1meviewer.R\nimport com.yenaly.han1meviewer.logic.model.SearchOption\nimport com.yenaly.han1meviewer.ui.adapter.HSearchTagAdapter\nimport com.yenaly.han1meviewer.util.createAlertDialog\nimport com.yenaly.han1meviewer.util.showWithBlurEffect\nimport com.yenaly.yenaly_libs.utils.findActivity\nimport com.yenaly.yenaly_libs.utils.view.SimpleFragmentStateAdapter\nimport com.yenaly.yenaly_libs.utils.view.attach\n\nclass HMultiChoicesDialog(\n    val context: Context,\n    @StringRes private val titleRes: Int,\n    private val hasSingleItem: Boolean = false\n) {\n\n    companion object {\n        const val UNKNOWN_ADAPTER = -1\n        var adapterMap: SparseArray<HSearchTagAdapter>? = null\n    }\n\n    private val pageAdapter = SimpleFragmentStateAdapter(context.findActivity())\n\n    private val coreView = View.inflate(context, R.layout.pop_up_hanime_search_tag, null)\n    private val tab = coreView.findViewById<TabLayout>(R.id.tl_tag)\n    private val page = coreView.findViewById<ViewPager2>(R.id.vp_tag)\n\n    val adapterMap: SparseArray<HSearchTagAdapter>\n    private val nameResList = mutableListOf<Int>()\n\n    private var onSave: ((AlertDialog) -> Unit)? = null\n    private var onReset: ((AlertDialog) -> Unit)? = null\n    private var onDismiss: DialogInterface.OnDismissListener? = null\n\n    private var isAdded = false\n\n    private val dialog = context.createAlertDialog {\n        setTitle(titleRes)\n        setPositiveButton(R.string.save, null)\n        setNeutralButton(R.string.reset, null)\n        setView(coreView)\n    }\n\n    init {\n        HMultiChoicesDialog.adapterMap = SparseArray()\n        adapterMap = HMultiChoicesDialog.adapterMap!!\n\n        page.adapter = pageAdapter\n        page.offscreenPageLimit = ViewPager2.OFFSCREEN_PAGE_LIMIT_DEFAULT\n\n        dialog.setOnShowListener { di ->\n            page.requestLayout()\n            val ad = di as AlertDialog\n            dialog.getButton(AlertDialog.BUTTON_POSITIVE).setOnClickListener {\n                onSave?.invoke(ad)\n            }\n            dialog.getButton(AlertDialog.BUTTON_NEUTRAL).setOnClickListener {\n                onReset?.invoke(ad)\n            }\n        }\n    }\n\n    private fun getTagScope(@StringRes scopeNameRes: Int): HSearchTagAdapter? =\n        adapterMap[scopeNameRes]\n\n    fun addTagScope(@StringRes scopeNameRes: Int?, items: List<SearchOption>?, spanCount: Int = 3) {\n        if (items.isNullOrEmpty() || isAdded) return\n        if (hasSingleItem) {\n            tab.isVisible = false\n            page.isUserInputEnabled = false\n            isAdded = true\n        }\n        val tagAdapter = HSearchTagAdapter()\n        adapterMap[scopeNameRes ?: UNKNOWN_ADAPTER] = tagAdapter\n        nameResList += scopeNameRes ?: UNKNOWN_ADAPTER\n        pageAdapter.addFragment {\n            HCheckBoxFragment.newInstance(\n                scopeNameRes ?: UNKNOWN_ADAPTER, items, spanCount\n            )\n        }\n    }\n\n    fun setOnSaveListener(action: (AlertDialog) -> Unit) {\n        onSave = action\n    }\n\n    fun setOnResetListener(action: (AlertDialog) -> Unit) {\n        onReset = action\n    }\n\n    fun setOnDismissListener(action: DialogInterface.OnDismissListener?) {\n        onDismiss = action\n    }\n\n    fun loadSavedTags(saved: SparseArray<Set<SearchOption>>) {\n        for (i in 0..<saved.size) {\n            val key = saved.keyAt(i)\n            val value = saved.valueAt(i)\n            getTagScope(key)?.let { adapter ->\n                adapter.checkedSet.clear()\n                adapter.checkedSet += value\n            }\n        }\n    }\n\n    @SuppressLint(\"NotifyDataSetChanged\")\n    fun clearAllChecks() {\n        adapterMap.valueIterator().forEach { adapter ->\n            if (adapter.checkedSet.isNotEmpty()) {\n                adapter.checkedSet.clear()\n                adapter.notifyDataSetChanged()\n            }\n        }\n    }\n\n    fun collectCheckedTags(): SparseArray<Set<SearchOption>> {\n        return SparseArray<Set<SearchOption>>().apply {\n            for (i in 0..<adapterMap.size) {\n                val key = adapterMap.keyAt(i)\n                val value = adapterMap.valueAt(i)\n                if (value.checkedSet.isNotEmpty()) {\n                    this[key] = value.checkedSet\n                }\n            }\n        }\n    }\n\n    fun show() {\n        if (!hasSingleItem) {\n            tab.attach(page) { tab, pos ->\n                tab.setText(nameResList[pos])\n            }\n        }\n        dialog.showWithBlurEffect(DialogInterface.OnDismissListener {\n            onDismiss?.onDismiss(it)\n            HMultiChoicesDialog.adapterMap = null\n        })\n    }\n}"
  },
  {
    "path": "app/src/main/java/com/yenaly/han1meviewer/ui/fragment/search/HTimePickerDialog.kt",
    "content": "package com.yenaly.han1meviewer.ui.fragment.search\n\nimport android.content.Context\nimport android.content.DialogInterface\nimport android.util.Log\nimport android.view.View\nimport android.widget.NumberPicker\nimport android.widget.TextView\nimport androidx.annotation.StringRes\nimport androidx.core.view.isVisible\nimport com.google.android.material.materialswitch.MaterialSwitch\nimport com.yenaly.han1meviewer.R\nimport com.yenaly.han1meviewer.util.createAlertDialog\nimport com.yenaly.han1meviewer.util.showWithBlurEffect\nimport java.util.Calendar\n\n\n\nclass HTimePickerDialog(\n    val context: Context,\n    @StringRes private val titleRes: Int,\n)\n{\n    enum class Mode {\n        YMD, YM, Y\n    }\n    data class DateSelection(\n        val year: Int? = null,\n        val month: Int? = null,\n        val day: Int? = null\n    )\n\n    private val coreView = View.inflate(context, R.layout.pop_up_hanime_time_picker, null)\n\n    private val yearSwitch: MaterialSwitch = coreView.findViewById(R.id.year_switch)\n    private val yearPicker: NumberPicker = coreView.findViewById(R.id.year)\n    private val monthPicker: NumberPicker = coreView.findViewById(R.id.month)\n    private val dayPicker: NumberPicker = coreView.findViewById(R.id.day)\n\n    private val yearText: TextView = coreView.findViewById(R.id.year_text)\n    private val monthText: TextView = coreView.findViewById(R.id.month_text)\n    private val dayText: TextView = coreView.findViewById(R.id.day_text)\n\n    private var onSave: ((DateSelection) -> Unit)? = null\n\n    private var onReset: ((DateSelection) -> Unit)? = null\n\n    private var onDismiss: DialogInterface.OnDismissListener? = null\n\n    private var oldMode: Mode = Mode.YMD\n    private var mode: Mode = Mode.YMD\n\n    private val dialog = context.createAlertDialog {\n        setTitle(titleRes)\n        setPositiveButton(R.string.save) {_, _ ->\n            var date = when (mode) {\n                Mode.YMD -> DateSelection(\n                    year = yearPicker.value,\n                    month = monthPicker.value,\n                    day = dayPicker.value\n                )\n                Mode.YM -> DateSelection(\n                    year = yearPicker.value,\n                    month = monthPicker.value\n                )\n                Mode.Y -> DateSelection(\n                    year = yearPicker.value\n                )\n            }\n            onSave?.invoke(date)\n        }\n        setNeutralButton(R.string.reset) { _, _ ->\n            var date = DateSelection()\n            onReset?.invoke(date)\n        }\n        setView(coreView)\n    }\n\n    init {\n        val currentYear = Calendar.getInstance().get(Calendar.YEAR)\n        yearPicker.minValue = 1990\n        yearPicker.maxValue = currentYear\n        yearPicker.value = currentYear\n        monthPicker.minValue = 1\n        monthPicker.maxValue = 12\n        dayPicker.minValue = 1\n        dayPicker.maxValue = 31\n\n        yearSwitch.setOnClickListener {\n            if (yearSwitch.isChecked) {\n                setTimePickerMode(Mode.Y)\n            } else {\n                setTimePickerMode(oldMode)\n            }\n        }\n    }\n\n    fun setMode(mode: Mode) {\n        oldMode = mode\n        setTimePickerMode(mode)\n    }\n\n    fun setDate(year: Int? = null, month: Int? = null, day: Int? = null) {\n        if (year != null) yearPicker.value = year\n        if (month != null) monthPicker.value = month\n        if (day != null) dayPicker.value = day\n    }\n\n    private fun setTimePickerMode(mode: Mode) {\n        this.mode = mode\n        when (mode) {\n            Mode.YMD -> {\n                yearSwitch.isChecked = false\n\n                yearPicker.isVisible = true\n                monthPicker.isVisible = true\n                dayPicker.isVisible = true\n\n                yearText.isVisible = true\n                monthText.isVisible = true\n                dayText.isVisible = true\n            }\n            Mode.YM -> {\n                yearSwitch.isChecked = false\n\n                yearPicker.isVisible = true\n                monthPicker.isVisible = true\n                dayPicker.isVisible = false\n\n                yearText.isVisible = true\n                monthText.isVisible = true\n                dayText.isVisible = false\n            }\n            Mode.Y -> {\n                yearSwitch.isChecked = true\n\n                yearPicker.isVisible = true\n                monthPicker.isVisible = false\n                dayPicker.isVisible = false\n\n                yearText.isVisible = true\n                monthText.isVisible = false\n                dayText.isVisible = false\n            }\n        }\n    }\n\n    fun setYearRange(minYear: Int? = null, maxYear: Int? = null) {\n        if (minYear != null) yearPicker.minValue = minYear\n        if (maxYear != null) yearPicker.maxValue = maxYear\n    }\n\n    fun setOnSaveListener(action: (DateSelection) -> Unit) {\n        onSave = action\n    }\n\n    fun setonResetListener(action: (DateSelection) -> Unit) {\n        onReset = action\n    }\n\n    fun setOnDismissListener(action: DialogInterface.OnDismissListener?) {\n        onDismiss = action\n    }\n\n    fun show() {\n        dialog.showWithBlurEffect({\n            onDismiss?.onDismiss(it)\n        })\n    }\n}"
  },
  {
    "path": "app/src/main/java/com/yenaly/han1meviewer/ui/fragment/search/SearchOptionsPopupFragment.kt",
    "content": "package com.yenaly.han1meviewer.ui.fragment.search\n\nimport android.app.Dialog\nimport android.content.DialogInterface\nimport android.os.Bundle\nimport android.util.Log\nimport android.view.LayoutInflater\nimport android.view.View\nimport android.widget.Checkable\nimport androidx.core.util.isNotEmpty\nimport androidx.core.view.isVisible\nimport androidx.fragment.app.activityViewModels\nimport androidx.lifecycle.lifecycleScope\nimport androidx.recyclerview.widget.LinearLayoutManager\nimport com.google.firebase.Firebase\nimport com.google.firebase.analytics.FirebaseAnalytics\nimport com.google.firebase.analytics.analytics\nimport com.google.firebase.analytics.logEvent\nimport com.lxj.xpopup.XPopup\nimport com.lxj.xpopup.core.BasePopupView\nimport com.lxj.xpopup.interfaces.SimpleCallback\nimport com.lxj.xpopupext.listener.TimePickerListener\nimport com.lxj.xpopupext.popup.TimePickerPopup\nimport com.yenaly.han1meviewer.FirebaseConstants\nimport com.yenaly.han1meviewer.Preferences.isAlreadyLogin\nimport com.yenaly.han1meviewer.R\nimport com.yenaly.han1meviewer.databinding.PopUpFragmentSearchOptionsBinding\nimport com.yenaly.han1meviewer.logic.model.SearchOption.Companion.get\nimport com.yenaly.han1meviewer.logic.state.WebsiteState\nimport com.yenaly.han1meviewer.ui.activity.SearchActivity\nimport com.yenaly.han1meviewer.ui.adapter.HSubscriptionAdapter\nimport com.yenaly.han1meviewer.ui.popup.HTimePickerPopup\nimport com.yenaly.han1meviewer.ui.viewmodel.MyListViewModel\nimport com.yenaly.han1meviewer.ui.viewmodel.SearchViewModel\nimport com.yenaly.han1meviewer.util.showAlertDialog\nimport com.yenaly.yenaly_libs.base.YenalyBottomSheetDialogFragment\nimport com.yenaly.yenaly_libs.utils.mapToArray\nimport com.yenaly.yenaly_libs.utils.showShortToast\nimport com.yenaly.yenaly_libs.utils.unsafeLazy\nimport kotlinx.coroutines.launch\nimport java.util.Calendar\nimport java.util.Date\n\n/**\n * @project Han1meViewer\n * @author Yenaly Liew\n * @time 2023/08/08 008 17:04\n */\nclass SearchOptionsPopupFragment :\n    YenalyBottomSheetDialogFragment<PopUpFragmentSearchOptionsBinding>() {\n\n    private val viewModel by activityViewModels<SearchViewModel>()\n    val myListViewModel by activityViewModels<MyListViewModel>()\n\n    /**\n     * 是否用户真正使用了高级搜索里面的功能\n     *\n     * 用于 Firebase 统计\n     */\n    private var isUserUsed = false\n\n    private var genres: Array<String>? = null\n    private var sortOptions: Array<String>? = null\n    private var durations: Array<String>? = null\n\n    private val subscriptionAdapter by unsafeLazy {\n        HSubscriptionAdapter()\n    }\n\n    var onSearchListener: () -> Unit = {}\n\n    override fun getViewBinding(layoutInflater: LayoutInflater) =\n        PopUpFragmentSearchOptionsBinding.inflate(layoutInflater)\n\n    override fun initData(savedInstanceState: Bundle?, dialog: Dialog) {\n        // #issue-199: 片长搜索官网取消了\n//        binding.duration.isAvailable = false\n        // 简单的厂商搜索官网取消了\n        binding.brand.isAvailable = false\n\n        initOptionsChecked()\n        initClick()\n        initSubscription()\n    }\n\n    private fun initOptionsChecked() {\n        binding.brand.isChecked = viewModel.brandMap.isNotEmpty()\n        binding.sortOption.isChecked = viewModel.sort != null\n        binding.duration.isChecked = viewModel.duration != null\n        binding.tag.isChecked = viewModel.tagMap.isNotEmpty()\n        binding.type.isChecked = viewModel.genre != null\n        binding.releaseDate.isChecked = viewModel.year != null || viewModel.month != null\n    }\n\n    private fun initClick() {\n        binding.type.apply {\n            setOnClickListener {\n                // typePopup.show()\n                if (genres == null) {\n                    genres = viewModel.genres.mapToArray { it.value }\n                }\n                requireContext().showAlertDialog(DialogInterface.OnDismissListener {\n                    logAdvSearchEvent(\"genres\")\n                }) {\n                    val index = viewModel.genres.indexOfFirst {\n                        it.searchKey == viewModel.genre\n                    }\n                    setTitle(R.string.type)\n                    setSingleChoiceItems(genres, index) { _, which ->\n                        viewModel.genre = viewModel.genres.getOrNull(which)?.searchKey\n                        isUserUsed = true\n                        initOptionsChecked()\n                    }\n                    setPositiveButton(R.string.save, null)\n                    setNeutralButton(R.string.reset) { _, _ ->\n                        viewModel.genre = null\n                        initOptionsChecked()\n                    }\n                }\n            }\n            setOnLongClickListener lc@{\n                showClearAllTagsDialog {\n                    viewModel.genre = null\n                    initOptionsChecked()\n                }\n                return@lc true\n            }\n        }\n        // deprecated\n        binding.brand.apply {\n            setOnClickListener {\n                HMultiChoicesDialog(context, R.string.brand, hasSingleItem = true).apply {\n                    addTagScope(null, viewModel.brands, spanCount = 2)\n                }.apply {\n                    loadSavedTags(viewModel.brandMap)\n                    setOnSaveListener {\n                        viewModel.brandMap = collectCheckedTags()\n                        initOptionsChecked()\n                        it.dismiss()\n                    }\n                    setOnResetListener {\n                        clearAllChecks()\n                        initOptionsChecked()\n                    }\n                }.show()\n            }\n            setOnLongClickListener lc@{\n                showClearAllTagsDialog {\n                    viewModel.brandMap.clear()\n                    initOptionsChecked()\n                }\n                return@lc true\n            }\n        }\n        binding.tag.apply {\n            setOnClickListener {\n                HMultiChoicesDialog(context, R.string.tag).apply {\n                    addTagScope(\n                        R.string.video_attr,\n                        viewModel.tags[R.string.video_attr],\n                        spanCount = 1\n                    )\n                    addTagScope(\n                        R.string.relationship,\n                        viewModel.tags[R.string.relationship],\n                        spanCount = 2\n                    )\n                    addTagScope(\n                        R.string.characteristics,\n                        viewModel.tags[R.string.characteristics]\n                    )\n                    addTagScope(\n                        R.string.appearance_and_figure,\n                        viewModel.tags[R.string.appearance_and_figure]\n                    )\n                    addTagScope(\n                        R.string.story_plot,\n                        viewModel.tags[R.string.story_plot]\n                    )\n                    addTagScope(\n                        R.string.sex_position,\n                        viewModel.tags[R.string.sex_position]\n                    )\n                }.apply {\n                    loadSavedTags(viewModel.tagMap)\n                    setOnSaveListener {\n                        viewModel.tagMap = collectCheckedTags()\n                        initOptionsChecked()\n                        isUserUsed = true\n                        it.dismiss()\n                    }\n                    setOnResetListener {\n                        clearAllChecks()\n                        initOptionsChecked()\n                    }\n                    setOnDismissListener {\n                        logAdvSearchEvent(\"tags\")\n                    }\n                }.show()\n            }\n            setOnLongClickListener lc@{\n                showClearAllTagsDialog {\n                    viewModel.tagMap.clear()\n                    initOptionsChecked()\n                }\n                return@lc true\n            }\n        }\n        binding.sortOption.apply {\n            setOnClickListener {\n                // sortOptionPopup.show()\n                if (sortOptions == null) {\n                    sortOptions = viewModel.sortOptions.mapToArray { it.value }\n                }\n                requireContext().showAlertDialog(DialogInterface.OnDismissListener {\n                    logAdvSearchEvent(\"sort_options\")\n                }) {\n                    val index = viewModel.sortOptions.indexOfFirst {\n                        it.searchKey == viewModel.sort\n                    }\n                    setTitle(R.string.sort_option)\n                    setSingleChoiceItems(sortOptions, index) { _, which ->\n                        viewModel.sort = viewModel.sortOptions.getOrNull(which)?.searchKey\n                        isUserUsed = true\n                        initOptionsChecked()\n                    }\n                    setPositiveButton(R.string.save, null)\n                    setNeutralButton(R.string.reset) { _, _ ->\n                        viewModel.sort = null\n                        initOptionsChecked()\n                    }\n                }\n            }\n            setOnLongClickListener lc@{\n                showClearAllTagsDialog {\n                    viewModel.sort = null\n                    initOptionsChecked()\n                }\n                return@lc true\n            }\n        }\n        // deprecated\n        binding.duration.apply {\n            setOnClickListener {\n                // durationPopup.show()\n                if (durations == null) {\n                    durations = viewModel.durations.mapToArray { it.value }\n                }\n                requireContext().showAlertDialog(DialogInterface.OnDismissListener {\n                    initOptionsChecked()\n                }) {\n                    val index = viewModel.durations.indexOfFirst {\n                        it.searchKey == viewModel.duration\n                    }\n                    setTitle(R.string.duration)\n                    setSingleChoiceItems(durations, index) { _, which ->\n                        viewModel.duration = viewModel.durations.getOrNull(which)?.searchKey\n                        initOptionsChecked()\n                    }\n                    setPositiveButton(R.string.save, null)\n                    setNeutralButton(R.string.reset) { _, _ ->\n                        viewModel.duration = null\n                        initOptionsChecked()\n                    }\n                }\n            }\n            setOnLongClickListener lc@{\n                showClearAllTagsDialog {\n                    viewModel.duration = null\n                    initOptionsChecked()\n                }\n                return@lc true\n            }\n        }\n        binding.releaseDate.apply {\n            setOnClickListener {\n                HTimePickerDialog(requireContext(), R.string.release_date).apply {\n                    setMode(HTimePickerDialog.Mode.YM)\n                    setDate(\n                        year = viewModel.year,\n                        month = viewModel.month\n                    )\n                    setOnSaveListener { dateSelection ->\n                        viewModel.year = dateSelection.year\n                        viewModel.month = dateSelection.month\n                        initOptionsChecked()\n                        isUserUsed = true\n                    }\n                    setOnDismissListener {\n                        logAdvSearchEvent(\"release_dates\")\n                    }\n                    setonResetListener {\n                        viewModel.year = null\n                        viewModel.month = null\n                        initOptionsChecked()\n                    }\n                }.show()\n            }\n            setOnLongClickListener lc@{\n                showClearAllTagsDialog {\n                    viewModel.year = null\n                    viewModel.month = null\n                    initOptionsChecked()\n                }\n                return@lc true\n            }\n        }\n\n        binding.search.setOnClickListener {\n            onSearchListener.invoke()\n        }\n    }\n\n    private fun initSubscription() {\n        binding.rvSubscription.layoutManager =\n            LinearLayoutManager(context, LinearLayoutManager.HORIZONTAL, false)\n        binding.rvSubscription.adapter = subscriptionAdapter\n        if (isAlreadyLogin) {\n            // 暂时只读取第一页\n            lifecycleScope.launch {\n                myListViewModel.subscription.subscriptionFlow.collect {\n                    binding.llSubscription.isVisible = it.isNotEmpty()\n                    subscriptionAdapter.submitList(it.map { subscription ->\n                        subscription.copy(\n                            isDeleteVisible = false,\n                            isCheckBoxVisible = subscription.name == viewModel.subscriptionBrand\n                        )\n                    })\n                }\n            }\n            lifecycleScope.launch {\n                myListViewModel.subscription.deleteSubscriptionFlow.collect { state ->\n                    when (state) {\n                        is WebsiteState.Success -> {\n                            showShortToast(R.string.delete_success)\n                            val position = state.info\n                            val item = subscriptionAdapter.getItem(position) ?: return@collect\n                            val activity = requireContext()\n                            if (activity is SearchActivity && item.name == activity.searchText) {\n                                activity.setSearchText(null)\n                            }\n                        }\n\n                        is WebsiteState.Error -> {\n                            showShortToast(R.string.delete_failed)\n                            state.throwable.printStackTrace()\n                        }\n\n                        WebsiteState.Loading -> Unit\n                    }\n                }\n            }\n        }\n    }\n\n    private inline fun Checkable.showClearAllTagsDialog(crossinline action: () -> Unit) {\n        if (isChecked) {\n            requireContext().showAlertDialog {\n                setTitle(R.string.alert)\n                setMessage(R.string.alert_cancel_all_tags)\n                setPositiveButton(R.string.confirm) { _, _ -> action.invoke() }\n                setNegativeButton(R.string.cancel, null)\n            }\n        }\n    }\n\n    private fun XPopup.Builder.setOptionsCheckedCallback(type: String) = apply {\n        setPopupCallback(object : SimpleCallback() {\n            override fun beforeDismiss(popupView: BasePopupView?) {\n                initOptionsChecked()\n            }\n\n            override fun onDismiss(popupView: BasePopupView?) {\n                logAdvSearchEvent(type)\n            }\n        })\n    }\n\n    private fun logAdvSearchEvent(type: String, used: Boolean = isUserUsed) {\n        Log.d(\"HFirebase\", \"logAdvSearchEvent: $type, $used\")\n        Firebase.analytics.logEvent(FirebaseConstants.ADV_SEARCH_OPT) {\n            // 判断当前点击类型\n            param(FirebaseAnalytics.Param.CONTENT_TYPE, type)\n            // 判断用户是否真正使用了高级搜索\n            param(\"used\", used.toString())\n        }\n        // 重置状态\n        isUserUsed = false\n    }\n}"
  },
  {
    "path": "app/src/main/java/com/yenaly/han1meviewer/ui/fragment/settings/DownloadSettingsFragment.kt",
    "content": "package com.yenaly.han1meviewer.ui.fragment.settings\n\nimport android.os.Bundle\nimport android.util.Log\nimport androidx.preference.SeekBarPreference\nimport com.yenaly.han1meviewer.HFileManager\nimport com.yenaly.han1meviewer.HFileManager.appDownloadFolder\nimport com.yenaly.han1meviewer.HFileManager.appExternalDownloadFolder\nimport com.yenaly.han1meviewer.HFileManager.getDownloadFolder\nimport com.yenaly.han1meviewer.Preferences\nimport com.yenaly.han1meviewer.R\nimport com.yenaly.han1meviewer.logic.network.interceptor.SpeedLimitInterceptor\nimport com.yenaly.han1meviewer.ui.activity.SettingsActivity\nimport com.yenaly.han1meviewer.ui.fragment.IToolbarFragment\nimport com.yenaly.han1meviewer.ui.fragment.settings.NetworkSettingsFragment.Companion.USE_BUILT_IN_HOSTS\nimport com.yenaly.han1meviewer.util.setSummaryConverter\nimport com.yenaly.han1meviewer.util.showAlertDialog\nimport com.yenaly.han1meviewer.worker.HanimeDownloadManagerV2\nimport com.yenaly.yenaly_libs.base.preference.LongClickablePreference\nimport com.yenaly.yenaly_libs.base.preference.MaterialSwitchPreference\nimport com.yenaly.yenaly_libs.base.settings.YenalySettingsFragment\nimport com.yenaly.yenaly_libs.utils.copyToClipboard\nimport com.yenaly.yenaly_libs.utils.formatBytesPerSecond\nimport com.yenaly.yenaly_libs.utils.mapToArray\nimport com.yenaly.yenaly_libs.utils.showShortToast\n\nclass DownloadSettingsFragment : YenalySettingsFragment(R.xml.settings_download),\n    IToolbarFragment<SettingsActivity> {\n\n    companion object {\n        const val DOWNLOAD_PATH = \"download_path\"\n        const val DOWNLOAD_COUNT_LIMIT = \"download_count_limit\"\n        const val DOWNLOAD_SPEED_LIMIT = \"download_speed_limit\"\n        const val USE_PRIVATE_DIRECTORY = \"use_private_directory\"\n    }\n\n    private val downloadPath\n            by safePreference<LongClickablePreference>(DOWNLOAD_PATH)\n    private val downloadCountLimit\n            by safePreference<SeekBarPreference>(DOWNLOAD_COUNT_LIMIT)\n    private val downloadSpeedLimit\n            by safePreference<SeekBarPreference>(DOWNLOAD_SPEED_LIMIT)\n\n    private val usePrivateDirectory\n            by safePreference<MaterialSwitchPreference>(USE_PRIVATE_DIRECTORY)\n\n    override fun onStart() {\n        super.onStart()\n        (activity as SettingsActivity).setupToolbar()\n    }\n\n    override fun onPreferencesCreated(savedInstanceState: Bundle?) {\n        downloadPath.apply {\n            val path = getDownloadFolder().path\n            summary = path\n            setOnPreferenceClickListener {\n                requireContext().showAlertDialog {\n                    setTitle(R.string.not_allow_to_change)\n                    setMessage(\n                        getString(R.string.detailed_path_s, path) + \"\\n\"\n                                + getString(R.string.long_press_pref_to_copy)\n                    )\n                    setPositiveButton(R.string.ok, null)\n                }\n\n                return@setOnPreferenceClickListener true\n            }\n            setOnPreferenceLongClickListener {\n                path.copyToClipboard()\n                showShortToast(R.string.copy_to_clipboard)\n                return@setOnPreferenceLongClickListener true\n            }\n        }\n        downloadCountLimit.apply {\n            setSummaryConverter(\n                defValue = HanimeDownloadManagerV2.MAX_CONCURRENT_DOWNLOAD_DEF,\n                converter = ::toDownloadCountLimitPrettyString\n            ) {\n                // HanimeDownloadManager.maxConcurrentDownloadCount = it\n                HanimeDownloadManagerV2.maxConcurrentDownloadCount = it\n            }\n        }\n        downloadSpeedLimit.apply {\n            min = 0\n            max = SpeedLimitInterceptor.SPEED_BYTES.lastIndex\n            setSummaryConverter(defValue = SpeedLimitInterceptor.NO_LIMIT_INDEX, converter = { i ->\n                SpeedLimitInterceptor.SPEED_BYTES[i].toDownloadSpeedPrettyString()\n            })\n        }\n        usePrivateDirectory.setOnPreferenceChangeListener { _, newValue ->\n            val usePrivate = newValue as Boolean\n            downloadPath.summary = if (usePrivate) appDownloadFolder.path else appExternalDownloadFolder.path\n            return@setOnPreferenceChangeListener true\n        }\n    }\n\n    private fun Long.toDownloadSpeedPrettyString(): String {\n        return if (this == 0L) {\n            getString(R.string.no_limit)\n        } else {\n            this.formatBytesPerSecond()\n        }\n    }\n\n    private fun toDownloadCountLimitPrettyString(value: Int): String {\n        return if (value == 0) {\n            getString(R.string.no_limit)\n        } else {\n            value.toString()\n        }\n    }\n\n    override fun SettingsActivity.setupToolbar() {\n        supportActionBar!!.setTitle(R.string.download_settings)\n    }\n}"
  },
  {
    "path": "app/src/main/java/com/yenaly/han1meviewer/ui/fragment/settings/HKeyframeSettingsFragment.kt",
    "content": "package com.yenaly.han1meviewer.ui.fragment.settings\n\nimport android.os.Bundle\nimport androidx.annotation.IntRange\nimport androidx.preference.Preference\nimport androidx.preference.PreferenceCategory\nimport androidx.preference.SeekBarPreference\nimport androidx.preference.SwitchPreferenceCompat\nimport com.yenaly.han1meviewer.R\nimport com.yenaly.han1meviewer.ui.activity.SettingsActivity\nimport com.yenaly.han1meviewer.ui.activity.SettingsRouter\nimport com.yenaly.han1meviewer.ui.fragment.IToolbarFragment\nimport com.yenaly.han1meviewer.ui.view.video.HJzvdStd\nimport com.yenaly.han1meviewer.util.setSummaryConverter\nimport com.yenaly.yenaly_libs.base.settings.YenalySettingsFragment\n\n/**\n * @project Han1meViewer\n * @author Yenaly Liew\n * @time 2023/11/15 015 10:59\n */\nclass HKeyframeSettingsFragment : YenalySettingsFragment(R.xml.settings_h_keyframe),\n    IToolbarFragment<SettingsActivity> {\n\n    companion object {\n        const val H_KEYFRAMES_ENABLE = \"h_keyframes_enable\"\n        const val H_KEYFRAME_MANAGE = \"h_keyframe_manage\"\n        const val SHOW_COMMENT_WHEN_COUNTDOWN = \"show_comment_when_countdown\"\n        const val SHARED_H_KEYFRAMES_ENABLE = \"shared_h_keyframes_enable\"\n        const val SHARED_H_KEYFRAMES_USE_FIRST = \"shared_h_keyframes_use_first\"\n        const val SHARED_H_KEYFRAME_MANAGE = \"shared_h_keyframe_manage\"\n        const val WHEN_COUNTDOWN_REMIND = \"when_countdown_remind\"\n\n        const val H_KEYFRAME_MANAGE_CATEGORY = \"h_keyframe_manage_category\"\n        const val H_KEYFRAME_SHARED_CATEGORY = \"shared_h_keyframes_category\"\n        const val H_KEYFRAME_CUSTOM_CATEGORY = \"h_keyframe_custom_category\"\n    }\n\n    private val hKeyframesEnable\n            by safePreference<SwitchPreferenceCompat>(H_KEYFRAMES_ENABLE)\n    private val hKeyframeManage\n            by safePreference<Preference>(H_KEYFRAME_MANAGE)\n\n    private val sharedHKeyframesEnable\n            by safePreference<SwitchPreferenceCompat>(SHARED_H_KEYFRAMES_ENABLE)\n    private val sharedHKeyframesUseFirst\n            by safePreference<SwitchPreferenceCompat>(SHARED_H_KEYFRAMES_USE_FIRST)\n    private val sharedHKeyframesManage\n            by safePreference<Preference>(SHARED_H_KEYFRAME_MANAGE)\n\n    private val showCommentWhenCountdown\n            by safePreference<SwitchPreferenceCompat>(SHOW_COMMENT_WHEN_COUNTDOWN)\n    private val whenCountdownRemind\n            by safePreference<SeekBarPreference>(WHEN_COUNTDOWN_REMIND)\n\n    private val manageCategory\n            by safePreference<PreferenceCategory>(H_KEYFRAME_MANAGE_CATEGORY)\n    private val sharedCategory\n            by safePreference<PreferenceCategory>(H_KEYFRAME_SHARED_CATEGORY)\n    private val customCategory\n            by safePreference<PreferenceCategory>(H_KEYFRAME_CUSTOM_CATEGORY)\n\n    override fun onStart() {\n        super.onStart()\n        (activity as SettingsActivity).setupToolbar()\n    }\n\n    override fun onPreferencesCreated(savedInstanceState: Bundle?) {\n        hKeyframesEnable.apply {\n            summary = keyframeTip(isChecked)\n            manageCategory.isVisible = isChecked\n            sharedCategory.isVisible = isChecked\n            customCategory.isVisible = isChecked\n            setOnPreferenceChangeListener { _, newValue ->\n                val isChecked = newValue as Boolean\n                summary = keyframeTip(isChecked)\n                manageCategory.isVisible = isChecked\n                sharedCategory.isVisible = isChecked\n                customCategory.isVisible = isChecked\n                return@setOnPreferenceChangeListener true\n            }\n        }\n        hKeyframeManage.apply {\n            setOnPreferenceClickListener {\n                SettingsRouter.with(this@HKeyframeSettingsFragment)\n                    .navigateWithinSettings(R.id.hKeyframesFragment)\n                return@setOnPreferenceClickListener true\n            }\n        }\n        sharedHKeyframesEnable.apply {\n            sharedHKeyframesUseFirst.isVisible = isChecked\n            sharedHKeyframesManage.isVisible = isChecked\n            setOnPreferenceChangeListener { _, newValue ->\n                val isChecked = newValue as Boolean\n                sharedHKeyframesUseFirst.isVisible = isChecked\n                sharedHKeyframesManage.isVisible = isChecked\n                return@setOnPreferenceChangeListener true\n            }\n        }\n        sharedHKeyframesManage.apply {\n            setOnPreferenceClickListener {\n                SettingsRouter.with(this@HKeyframeSettingsFragment)\n                    .navigateWithinSettings(R.id.sharedHKeyframesFragment)\n                return@setOnPreferenceClickListener true\n            }\n        }\n        whenCountdownRemind.apply {\n            setSummaryConverter(\n                defValue = HJzvdStd.DEF_COUNTDOWN_SEC,\n                converter = ::toPrettyCountdownRemindString\n            )\n        }\n    }\n\n    private fun toPrettyCountdownRemindString(@IntRange(from = 5, to = 30) value: Int): String {\n        return buildString {\n            val countdown = value\n            append(getString(R.string.will_remind_before_d_seconds, countdown))\n            if (countdown == HJzvdStd.DEF_COUNTDOWN_SEC) append(\" (${getString(R.string.default_)})\")\n        }\n    }\n\n    private fun keyframeTip(isChecked: Boolean) = if (isChecked) {\n        getString(R.string.h_keyframes_enable_tip)\n    } else {\n        getString(R.string.h_keyframes_disable_tip)\n    }\n\n    override fun SettingsActivity.setupToolbar() {\n        supportActionBar!!.setTitle(R.string.h_keyframe_settings)\n    }\n}"
  },
  {
    "path": "app/src/main/java/com/yenaly/han1meviewer/ui/fragment/settings/HKeyframesFragment.kt",
    "content": "package com.yenaly.han1meviewer.ui.fragment.settings\n\nimport android.os.Bundle\nimport android.view.LayoutInflater\nimport android.view.ViewGroup\nimport androidx.core.view.isVisible\nimport androidx.fragment.app.activityViewModels\nimport androidx.lifecycle.flowWithLifecycle\nimport androidx.lifecycle.lifecycleScope\nimport androidx.recyclerview.widget.LinearLayoutManager\nimport com.yenaly.han1meviewer.R\nimport com.yenaly.han1meviewer.databinding.FragmentHKeyframesBinding\nimport com.yenaly.han1meviewer.logic.entity.HKeyframeEntity\nimport com.yenaly.han1meviewer.ui.StateLayoutMixin\nimport com.yenaly.han1meviewer.ui.activity.SettingsActivity\nimport com.yenaly.han1meviewer.ui.adapter.HKeyframesRvAdapter\nimport com.yenaly.han1meviewer.ui.fragment.IToolbarFragment\nimport com.yenaly.han1meviewer.ui.viewmodel.SettingsViewModel\nimport com.yenaly.han1meviewer.util.setStateViewLayout\nimport com.yenaly.han1meviewer.util.showAlertDialog\nimport com.yenaly.yenaly_libs.base.YenalyFragment\nimport com.yenaly.yenaly_libs.utils.decodeFromStringByBase64\nimport com.yenaly.yenaly_libs.utils.showShortToast\nimport com.yenaly.yenaly_libs.utils.textFromClipboard\nimport com.yenaly.yenaly_libs.utils.unsafeLazy\nimport kotlinx.coroutines.launch\nimport kotlinx.serialization.json.Json\nimport kotlin.concurrent.thread\n\n/**\n * @project Han1meViewer\n * @author Yenaly Liew\n * @time 2023/11/13 013 18:46\n */\nclass HKeyframesFragment : YenalyFragment<FragmentHKeyframesBinding>(),\n    IToolbarFragment<SettingsActivity>, StateLayoutMixin {\n\n    val viewModel by activityViewModels<SettingsViewModel>()\n\n    private val adapter by unsafeLazy { HKeyframesRvAdapter() }\n\n    private val hKeyframesShareRegex = Regex(\">>>(.+)<<<\")\n\n    override fun getViewBinding(\n        inflater: LayoutInflater,\n        container: ViewGroup?\n    ): FragmentHKeyframesBinding {\n        return FragmentHKeyframesBinding.inflate(inflater, container, false)\n    }\n\n    override fun onStart() {\n        super.onStart()\n        (activity as SettingsActivity).setupToolbar()\n    }\n\n    override fun initData(savedInstanceState: Bundle?) {\n        binding.btnUp.isVisible = false\n        binding.btnDown.isVisible = false\n        binding.rvKeyframe.layoutManager = LinearLayoutManager(context)\n        binding.rvKeyframe.adapter = adapter\n        adapter.setStateViewLayout(R.layout.layout_empty_view)\n    }\n\n    override fun bindDataObservers() {\n        viewLifecycleOwner.lifecycleScope.launch {\n            viewModel.loadAllHKeyframes().flowWithLifecycle(lifecycle)\n                .collect { entity ->\n                    adapter.submitList(entity)\n                }\n        }\n    }\n\n    private fun addHKeyframes() {\n        thread {\n            textFromClipboard?.let { text ->\n                val matchResult = hKeyframesShareRegex.find(text)\n                if (matchResult != null) {\n                    val (toBase64) = matchResult.destructured\n                    val toJson = toBase64.decodeFromStringByBase64()\n                    val entity = Json.decodeFromString<HKeyframeEntity>(toJson)\n                    activity?.runOnUiThread {\n                        context?.showAlertDialog {\n                            setTitle(R.string.h_keyframes_shared_by_other_detected)\n                            setMessage(\n                                getString(\n                                    R.string.shared_h_keyframe_detected_msg,\n                                    entity.title,\n                                    entity.videoCode,\n                                    entity.keyframes.size\n                                ).trimIndent()\n                            )\n                            setPositiveButton(R.string.confirm) { _, _ ->\n                                viewModel.insertHKeyframes(entity.copy(lastModifiedTime = System.currentTimeMillis()))\n                            }\n                            setNegativeButton(R.string.cancel, null)\n                        }\n                    }\n\n                } else {\n                    activity?.runOnUiThread {\n                        showShortToast(R.string.h_keyframes_shared_by_other_not_detected)\n                    }\n                }\n            } ?: activity?.runOnUiThread {\n                showShortToast(R.string.h_keyframes_shared_by_other_not_detected)\n            }\n        }\n    }\n\n    override fun SettingsActivity.setupToolbar() {\n        supportActionBar!!.setTitle(R.string.h_keyframe_manage)\n        this@HKeyframesFragment.apply {\n            addMenu(R.menu.menu_h_keyframes_toolbar, viewLifecycleOwner) {\n                when (it.itemId) {\n                    R.id.tb_add -> addHKeyframes()\n                }\n                return@addMenu true\n            }\n        }\n    }\n}"
  },
  {
    "path": "app/src/main/java/com/yenaly/han1meviewer/ui/fragment/settings/HomeSettingsFragment.kt",
    "content": "package com.yenaly.han1meviewer.ui.fragment.settings\n\nimport android.content.Context\nimport android.content.Intent\nimport android.content.pm.PackageManager\nimport android.os.Build\nimport android.os.Bundle\nimport android.os.Handler\nimport android.os.Looper\nimport android.provider.Settings\nimport android.view.View\nimport androidx.annotation.RequiresApi\nimport androidx.core.content.edit\nimport androidx.core.net.toUri\nimport androidx.core.text.parseAsHtml\nimport androidx.lifecycle.Lifecycle\nimport androidx.lifecycle.lifecycleScope\nimport androidx.lifecycle.repeatOnLifecycle\nimport androidx.preference.Preference\nimport androidx.preference.SeekBarPreference\nimport androidx.preference.SwitchPreferenceCompat\nimport com.google.firebase.Firebase\nimport com.google.firebase.analytics.analytics\nimport com.yenaly.han1meviewer.BuildConfig\nimport com.yenaly.han1meviewer.HA1_GITHUB_FORUM_URL\nimport com.yenaly.han1meviewer.HA1_GITHUB_ISSUE_URL\nimport com.yenaly.han1meviewer.HA1_GITHUB_RELEASES_URL\nimport com.yenaly.han1meviewer.Preferences\nimport com.yenaly.han1meviewer.R\nimport com.yenaly.han1meviewer.logic.state.WebsiteState\nimport com.yenaly.han1meviewer.ui.activity.SettingsActivity\nimport com.yenaly.han1meviewer.ui.activity.SettingsRouter\nimport com.yenaly.han1meviewer.ui.fragment.IToolbarFragment\nimport com.yenaly.han1meviewer.ui.view.pref.HPrivacyPreference\nimport com.yenaly.han1meviewer.ui.view.pref.MaterialDialogPreference\nimport com.yenaly.han1meviewer.ui.viewmodel.AppViewModel\nimport com.yenaly.han1meviewer.util.setSummaryConverter\nimport com.yenaly.han1meviewer.util.showAlertDialog\nimport com.yenaly.han1meviewer.util.showUpdateDialog\nimport com.yenaly.han1meviewer.util.showWithBlurEffect\nimport com.yenaly.yenaly_libs.ActivityManager\nimport com.yenaly.yenaly_libs.base.settings.YenalySettingsFragment\nimport com.yenaly.yenaly_libs.utils.browse\nimport com.yenaly.yenaly_libs.utils.folderSize\nimport com.yenaly.yenaly_libs.utils.formatFileSizeV2\nimport com.yenaly.yenaly_libs.utils.showShortToast\nimport kotlinx.coroutines.CoroutineScope\nimport kotlinx.coroutines.Dispatchers\nimport kotlinx.coroutines.launch\nimport kotlinx.coroutines.withContext\nimport kotlinx.datetime.Instant\nimport kotlinx.datetime.LocalDateTime\nimport kotlinx.datetime.TimeZone\nimport kotlinx.datetime.format\nimport kotlinx.datetime.toLocalDateTime\n\n/**\n * @project Han1meViewer\n * @author Yenaly Liew\n * @time 2022/07/01 001 14:25\n */\nclass HomeSettingsFragment : YenalySettingsFragment(R.xml.settings_home),\n    IToolbarFragment<SettingsActivity> {\n\n    companion object {\n        const val VIDEO_LANGUAGE = \"video_language\"\n        const val PLAYER_SETTINGS = \"player_settings\"\n        const val H_KEYFRAME_SETTINGS = \"h_keyframe_settings\"\n        const val UPDATE = \"update\"\n        const val ABOUT = \"about\"\n        const val CLEAR_CACHE = \"clear_cache\"\n        const val SUBMIT_BUG = \"submit_bug\"\n        const val FORUM = \"forum\"\n        const val NETWORK_SETTINGS = \"network_settings\"\n        const val APPLY_DEEP_LINKS = \"apply_deep_links\"\n        const val DOWNLOAD_SETTINGS = \"download_settings\"\n\n        const val LAST_UPDATE_POPUP_TIME = \"last_update_popup_time\"\n        const val UPDATE_POPUP_INTERVAL_DAYS = \"update_popup_interval_days\"\n        const val USE_CI_UPDATE_CHANNEL = \"use_ci_update_channel\"\n\n        const val USE_ANALYTICS = \"use_analytics\"\n\n        const val ALLOW_PIP = \"allow_pip\"\n    }\n\n    private val videoLanguage\n            by safePreference<MaterialDialogPreference>(VIDEO_LANGUAGE)\n    private val playerSettings\n            by safePreference<Preference>(PLAYER_SETTINGS)\n    private val hKeyframeSettings\n            by safePreference<Preference>(H_KEYFRAME_SETTINGS)\n    private val downloadSettings\n            by safePreference<Preference>(DOWNLOAD_SETTINGS)\n    private val update\n            by safePreference<Preference>(UPDATE)\n    private val useCIUpdateChannel\n            by safePreference<SwitchPreferenceCompat>(USE_CI_UPDATE_CHANNEL)\n    private val updatePopupIntervalDays\n            by safePreference<SeekBarPreference>(UPDATE_POPUP_INTERVAL_DAYS)\n    private val about\n            by safePreference<Preference>(ABOUT)\n    private val clearCache\n            by safePreference<Preference>(CLEAR_CACHE)\n    private val submitBug\n            by safePreference<Preference>(SUBMIT_BUG)\n    private val forum\n            by safePreference<Preference>(FORUM)\n    private val networkSettings\n            by safePreference<Preference>(NETWORK_SETTINGS)\n    private val applyDeepLinks\n            by safePreference<Preference>(APPLY_DEEP_LINKS)\n    private val useAnalytics\n            by safePreference<HPrivacyPreference>(USE_ANALYTICS)\n\n    private val allowPIP\n            by safePreference<SwitchPreferenceCompat>(ALLOW_PIP)\n\n    private var checkUpdateTimes = 0\n\n    override fun onStart() {\n        super.onStart()\n        (activity as SettingsActivity).setupToolbar()\n    }\n\n    override fun onPreferencesCreated(savedInstanceState: Bundle?) {\n        videoLanguage.apply {\n\n            // 從 xml 轉移至此\n            entries = arrayOf(\n                getString(R.string.traditional_chinese),\n                getString(R.string.simplified_chinese)\n            )\n            entryValues = arrayOf(\"zh-CHT\", \"zh-CHS\")\n            // 不能直接用 defaultValue 设置，没效果\n            if (value == null) setValueIndex(0)\n\n            setOnPreferenceChangeListener { _, newValue ->\n                if (newValue != Preferences.videoLanguage) {\n                    requireContext().showAlertDialog {\n                        setCancelable(false)\n                        setTitle(R.string.attention)\n                        setMessage(\n                            getString(\n                                R.string.restart_or_not_working,\n                                getString(R.string.video_language)\n                            )\n                        )\n                        setPositiveButton(R.string.confirm) { _, _ ->\n                            ActivityManager.restart(killProcess = true)\n                        }\n                        setNegativeButton(R.string.cancel, null)\n                    }\n                }\n                return@setOnPreferenceChangeListener true\n            }\n        }\n        playerSettings.setOnPreferenceClickListener {\n            SettingsRouter.with(this).navigateWithinSettings(R.id.playerSettingsFragment)\n            return@setOnPreferenceClickListener true\n        }\n        hKeyframeSettings.setOnPreferenceClickListener {\n            SettingsRouter.with(this).navigateWithinSettings(R.id.hKeyframeSettingsFragment)\n            return@setOnPreferenceClickListener true\n        }\n        downloadSettings.setOnPreferenceClickListener {\n            SettingsRouter.with(this).navigateWithinSettings(R.id.downloadSettingsFragment)\n            return@setOnPreferenceClickListener true\n        }\n        about.apply {\n            title = buildString {\n                append(getString(R.string.about))\n                append(\" \")\n                append(getString(R.string.hanime_app_name))\n            }\n            summary = getString(R.string.current_version, \"v${BuildConfig.VERSION_NAME}\")\n        }\n        clearCache.apply {\n            val cacheDir = context.cacheDir\n            var folderSize = cacheDir?.folderSize ?: 0L\n            summary = generateClearCacheSummary(folderSize)\n            setOnPreferenceClickListener {\n                if (folderSize != 0L) {\n                    context.showAlertDialog {\n                        setTitle(R.string.sure_to_clear)\n                        setMessage(R.string.sure_to_clear_cache)\n                        setPositiveButton(R.string.confirm) { _, _ ->\n                            CoroutineScope(Dispatchers.IO).launch {\n                                if (cacheDir?.deleteRecursively() == true) {\n                                    folderSize = cacheDir.folderSize\n                                    withContext(Dispatchers.Main) {\n                                        showShortToast(R.string.clear_success)\n                                        summary = generateClearCacheSummary(folderSize)\n                                    }\n                                } else {\n                                    folderSize = cacheDir.folderSize\n                                    withContext(Dispatchers.Main) {\n                                        showShortToast(R.string.clear_failed)\n                                        summary = generateClearCacheSummary(folderSize)\n                                    }\n                                }\n                            }\n                        }\n                        setNegativeButton(R.string.cancel, null)\n                    }\n                } else showShortToast(R.string.cache_empty)\n                return@setOnPreferenceClickListener true\n            }\n        }\n        submitBug.apply {\n            setOnPreferenceClickListener {\n                browse(HA1_GITHUB_ISSUE_URL)\n                return@setOnPreferenceClickListener true\n            }\n        }\n        forum.apply {\n            setOnPreferenceClickListener {\n                browse(HA1_GITHUB_FORUM_URL)\n                return@setOnPreferenceClickListener true\n            }\n        }\n        networkSettings.apply {\n            setOnPreferenceClickListener {\n                SettingsRouter.with(this@HomeSettingsFragment)\n                    .navigateWithinSettings(R.id.networkSettingsFragment)\n                return@setOnPreferenceClickListener true\n            }\n        }\n        applyDeepLinks.apply {\n            if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.S) {\n                isVisible = true\n                setOnPreferenceClickListener {\n                    showApplyDeepLinksDialog(it.context)\n                    return@setOnPreferenceClickListener true\n                }\n            } else {\n                isVisible = false\n            }\n        }\n        useCIUpdateChannel.apply {\n            setOnPreferenceChangeListener { _, _ ->\n                AppViewModel.getLatestVersion()\n                return@setOnPreferenceChangeListener true\n            }\n        }\n        updatePopupIntervalDays.apply {\n            setSummaryConverter(defValue = 0, converter = ::toIntervalDaysPrettyString)\n        }\n        useAnalytics.apply {\n            setOnPreferenceChangeListener { _, newValue ->\n                Firebase.analytics.setAnalyticsCollectionEnabled(newValue as Boolean)\n                return@setOnPreferenceChangeListener true\n            }\n            setOnPreferenceLongClickListener {\n                privacyDialog.showWithBlurEffect()\n                return@setOnPreferenceLongClickListener true\n            }\n        }\n\n        allowPIP.apply {\n            setOnPreferenceChangeListener { preference, newValue ->\n                val enabled = newValue as Boolean\n                if (enabled) {\n                    val hasPip = context.packageManager.hasSystemFeature(PackageManager.FEATURE_PICTURE_IN_PICTURE)\n\n                    preference.sharedPreferences?.edit {\n                        putBoolean(preference.key, hasPip)\n                    }\n\n                    if (preference is SwitchPreferenceCompat) {\n                        preference.isChecked = hasPip\n                    }\n                    return@setOnPreferenceChangeListener hasPip\n                }\n                return@setOnPreferenceChangeListener true\n            }\n        }\n    }\n\n    override fun onViewCreated(view: View, savedInstanceState: Bundle?) {\n        super.onViewCreated(view, savedInstanceState)\n        initFlow()\n    }\n\n    private fun initFlow() {\n        viewLifecycleOwner.lifecycleScope.launch {\n            repeatOnLifecycle(Lifecycle.State.STARTED) {\n                AppViewModel.versionFlow.collect { state ->\n                    when (state) {\n                        is WebsiteState.Error -> {\n                            checkUpdateTimes++\n                            update.setSummary(R.string.check_update_failed)\n                            update.setOnPreferenceClickListener {\n                                if (checkUpdateTimes > 2) {\n                                    showUpdateFailedDialog(it.context)\n                                } else {\n                                    AppViewModel.getLatestVersion()\n                                }\n                                return@setOnPreferenceClickListener true\n                            }\n                        }\n\n                        is WebsiteState.Loading -> {\n                            update.setSummary(R.string.checking_update)\n                            update.onPreferenceClickListener = null\n                        }\n\n                        is WebsiteState.Success -> {\n                            if (state.info == null) {\n                                update.setSummary(R.string.already_latest_update)\n                                update.onPreferenceClickListener = null\n                            } else {\n                                update.summary =\n                                    getString(R.string.check_update_success, state.info.version)\n                                update.setOnPreferenceClickListener {\n                                    viewLifecycleOwner.lifecycleScope.launch {\n                                        it.context.showUpdateDialog(state.info)\n                                    }\n                                    return@setOnPreferenceClickListener true\n                                }\n                            }\n                        }\n                    }\n                }\n            }\n        }\n    }\n\n    // #issue-124: Support deep links.\n    @RequiresApi(Build.VERSION_CODES.S)\n    private fun showApplyDeepLinksDialog(context: Context) {\n        context.showAlertDialog {\n            setTitle(R.string.apply_deep_links)\n            setView(R.layout.dialog_apply_deep_links)\n            setPositiveButton(R.string.go_to_settings) { _, _ ->\n                // #issue-197: 有些手机不支持直接从应用里跳转到深层链接界面\n                // 这个权限是一个系统级权限，所以没办法，不支持的手机只能自己找地方开了。\n                try {\n                    val intent = Intent().apply {\n                        action = Settings.ACTION_APP_OPEN_BY_DEFAULT_SETTINGS\n                        addCategory(Intent.CATEGORY_DEFAULT)\n                        data = \"package:${context.packageName}\".toUri()\n                        flags = Intent.FLAG_ACTIVITY_NO_HISTORY or\n                                Intent.FLAG_ACTIVITY_EXCLUDE_FROM_RECENTS\n                    }\n                    requireActivity().startActivity(intent)\n                } catch (e: Exception) {\n                    // 竟然还有手机不支持打开的\n                    showShortToast(R.string.action_app_open_by_default_settings_not_support)\n                    e.printStackTrace()\n                }\n            }\n            setNegativeButton(R.string.cancel, null)\n        }\n    }\n\n    private fun showUpdateFailedDialog(context: Context) {\n        context.showAlertDialog {\n            setTitle(R.string.do_not_check_update_again)\n            setMessage(getString(R.string.update_failed_tips).trimIndent())\n            setPositiveButton(R.string.take_me_to_download) { _, _ ->\n                browse(HA1_GITHUB_RELEASES_URL)\n            }\n            setNegativeButton(R.string.cancel, null)\n        }\n    }\n\n    private fun generateClearCacheSummary(size: Long): CharSequence {\n        return getString(R.string.cache_usage_summary, size.formatFileSizeV2()).parseAsHtml()\n//        return spannable {\n//            size.formatFileSizeV2().span {\n//                style(Typeface.BOLD)\n//            }\n//            \" \".text()\n//            getString(R.string.cache_occupy).text()\n//        }\n    }\n\n    private fun toIntervalDaysPrettyString(value: Int): String {\n        val lastUpdatePopupTime = Preferences.lastUpdatePopupTime\n        val msg = if (lastUpdatePopupTime == 0L) {\n            getString(R.string.no_update_popup_yet)\n        } else {\n            getString(\n                R.string.last_update_popup_check_time,\n                Instant.fromEpochSeconds(Preferences.lastUpdatePopupTime)\n                    .toLocalDateTime(TimeZone.currentSystemDefault())\n                    .format(LocalDateTime.Formats.ISO)\n            )\n        }\n        return when (value) {\n            0 -> getString(R.string.at_any_time)\n            else -> getString(R.string.which_days, value)\n        } + \"\\n\" + msg\n    }\n\n    override fun SettingsActivity.setupToolbar() {\n        supportActionBar!!.setTitle(R.string.settings)\n    }\n}"
  },
  {
    "path": "app/src/main/java/com/yenaly/han1meviewer/ui/fragment/settings/NetworkSettingsFragment.kt",
    "content": "package com.yenaly.han1meviewer.ui.fragment.settings\n\nimport android.annotation.SuppressLint\nimport android.os.Bundle\nimport android.view.View\nimport androidx.annotation.IdRes\nimport androidx.annotation.LayoutRes\nimport androidx.appcompat.app.AlertDialog\nimport androidx.core.content.edit\nimport androidx.preference.Preference\nimport com.google.android.material.chip.ChipGroup\nimport com.google.android.material.textfield.TextInputEditText\nimport com.yenaly.han1meviewer.EMPTY_STRING\nimport com.yenaly.han1meviewer.HANIME_ALTER_BASE_URL\nimport com.yenaly.han1meviewer.HANIME_ALTER_HOSTNAME\nimport com.yenaly.han1meviewer.HANIME_MAIN_BASE_URL\nimport com.yenaly.han1meviewer.HANIME_MAIN_HOSTNAME\nimport com.yenaly.han1meviewer.Preferences\nimport com.yenaly.han1meviewer.R\nimport com.yenaly.han1meviewer.logic.network.HProxySelector\nimport com.yenaly.han1meviewer.logic.network.HanimeNetwork\nimport com.yenaly.han1meviewer.logout\nimport com.yenaly.han1meviewer.ui.activity.SettingsActivity\nimport com.yenaly.han1meviewer.ui.fragment.IToolbarFragment\nimport com.yenaly.han1meviewer.ui.view.pref.MaterialDialogPreference\nimport com.yenaly.han1meviewer.util.createAlertDialog\nimport com.yenaly.han1meviewer.util.showAlertDialog\nimport com.yenaly.han1meviewer.util.showWithBlurEffect\nimport com.yenaly.yenaly_libs.ActivityManager\nimport com.yenaly.yenaly_libs.base.preference.MaterialSwitchPreference\nimport com.yenaly.yenaly_libs.base.settings.YenalySettingsFragment\nimport com.yenaly.yenaly_libs.utils.showShortToast\nimport com.yenaly.yenaly_libs.utils.unsafeLazy\n\n/**\n * @project Han1meViewer\n * @author Yenaly Liew\n * @time 2024/03/10 010 18:30\n */\nclass NetworkSettingsFragment : YenalySettingsFragment(R.xml.settings_network),\n    IToolbarFragment<SettingsActivity> {\n\n    companion object {\n        const val PROXY = \"proxy\"\n        const val PROXY_TYPE = \"proxy_type\"\n        const val PROXY_IP = \"proxy_ip\"\n        const val PROXY_PORT = \"proxy_port\"\n        const val DOMAIN_NAME = \"domain_name\"\n        const val USE_BUILT_IN_HOSTS = \"use_built_in_hosts\"\n        const val PING_TEST = \"ping_test\"\n    }\n\n    private val proxy\n            by safePreference<Preference>(PROXY)\n    private val domainName\n            by safePreference<MaterialDialogPreference>(DOMAIN_NAME)\n    private val useBuiltInHosts\n            by safePreference<MaterialSwitchPreference>(USE_BUILT_IN_HOSTS)\n\n    private val proxyDialog by unsafeLazy {\n        ProxyDialog(proxy, R.layout.dialog_proxy)\n    }\n\n    override fun onStart() {\n        super.onStart()\n        (activity as SettingsActivity).setupToolbar()\n    }\n\n    override fun onPreferencesCreated(savedInstanceState: Bundle?) {\n        proxy.apply {\n            summary = generateProxySummary(\n                Preferences.proxyType,\n                Preferences.proxyIp,\n                Preferences.proxyPort\n            )\n            setOnPreferenceClickListener {\n                proxyDialog.show()\n                return@setOnPreferenceClickListener true\n            }\n        }\n        domainName.apply {\n            entries = arrayOf(\n                \"$HANIME_MAIN_HOSTNAME (${getString(R.string.default_)})\",\n                \"$HANIME_ALTER_HOSTNAME (${getString(R.string.alternative)})\"\n            )\n            entryValues = arrayOf(HANIME_MAIN_BASE_URL, HANIME_ALTER_BASE_URL)\n            if (value == null) setValueIndex(0)\n\n            setOnPreferenceChangeListener { _, newValue ->\n                val origin = Preferences.baseUrl\n                if (newValue != origin) {\n                    requireContext().showAlertDialog {\n                        setCancelable(false)\n                        setTitle(R.string.attention)\n                        setMessage(getString(R.string.domain_change_tips).trimIndent())\n                        setPositiveButton(R.string.confirm) { _, _ ->\n                            logout()\n                            ActivityManager.restart(killProcess = true)\n                        }\n                        setNegativeButton(R.string.cancel) { _, _ ->\n                            domainName.value = origin\n                        }\n                    }\n                }\n                return@setOnPreferenceChangeListener true\n            }\n        }\n        useBuiltInHosts.apply {\n            setOnPreferenceChangeListener { _, _ ->\n                requireContext().showAlertDialog {\n                    setCancelable(false)\n                    setTitle(R.string.attention)\n                    setMessage(getString(R.string.restart_or_not_working, EMPTY_STRING))\n                    setPositiveButton(R.string.confirm) { _, _ ->\n                        ActivityManager.restart(killProcess = true)\n                    }\n                    setNegativeButton(R.string.cancel, null)\n                }\n                return@setOnPreferenceChangeListener true\n            }\n        }\n    }\n\n    private fun generateProxySummary(type: Int, ip: String, port: Int): CharSequence {\n        return when (type) {\n            HProxySelector.TYPE_DIRECT -> getString(R.string.direct)\n            HProxySelector.TYPE_SYSTEM -> getString(R.string.system_proxy)\n            HProxySelector.TYPE_HTTP -> getString(R.string.http_proxy, ip, port)\n            HProxySelector.TYPE_SOCKS -> getString(R.string.socks_proxy, ip, port)\n            else -> getString(R.string.direct)\n        }\n    }\n\n    inner class ProxyDialog(proxyPref: Preference, @LayoutRes layoutRes: Int) {\n\n        private val dialog: AlertDialog\n\n        private val cgTypes: ChipGroup\n        private val etIp: TextInputEditText\n        private val etPort: TextInputEditText\n\n        init {\n            val view = View.inflate(context, layoutRes, null)\n            cgTypes = view.findViewById(R.id.cg_types)\n            etIp = view.findViewById(R.id.et_ip)\n            etPort = view.findViewById(R.id.et_port)\n            initView()\n            dialog = proxyPref.context.createAlertDialog {\n                setView(view)\n                setCancelable(false)\n                setTitle(R.string.proxy)\n                setPositiveButton(R.string.confirm, null) // Set to null. We override the onclick.\n                setNegativeButton(R.string.cancel, null)\n            }\n            dialog.setOnShowListener {\n                dialog.getButton(AlertDialog.BUTTON_POSITIVE).setOnClickListener {\n                    val ip = etIp.text?.toString().orEmpty()\n                    val port = etPort.text?.toString()?.toIntOrNull() ?: -1\n                    val isValid = checkValid(ip, port)\n                    if (isValid) {\n                        val proxyType = proxyType\n                        Preferences.preferenceSp.edit(commit = true) {\n                            putInt(PROXY_TYPE, proxyType)\n                            putString(PROXY_IP, ip)\n                            putInt(PROXY_PORT, port)\n                        }\n                        // 重建相关联的所有网络请求\n                        HProxySelector.rebuildNetwork()\n                        HanimeNetwork.rebuildNetwork()\n                        proxyPref.summary = generateProxySummary(proxyType, ip, port)\n                        dialog.dismiss()\n                    } else {\n                        showShortToast(\"Invalid IP(v4) or Port(0..65535)\")\n                    }\n                }\n            }\n        }\n\n        @SuppressLint(\"SetTextI18n\")\n        private fun initView() {\n            when (Preferences.proxyType) {\n                HProxySelector.TYPE_DIRECT -> cgTypes.check(R.id.chip_direct)\n                HProxySelector.TYPE_SYSTEM -> cgTypes.check(R.id.chip_system_proxy)\n                HProxySelector.TYPE_HTTP -> cgTypes.check(R.id.chip_http)\n                HProxySelector.TYPE_SOCKS -> cgTypes.check(R.id.chip_socks)\n            }\n            val prefIp = Preferences.proxyIp\n            val prefPort = Preferences.proxyPort\n            if (prefIp.isNotBlank() && prefPort != -1) {\n                etIp.setText(prefIp)\n                etPort.setText(prefPort.toString())\n            }\n            enableView(cgTypes.checkedChipId)\n            cgTypes.setOnCheckedStateChangeListener { _, checkedIds ->\n                enableView(checkedIds.first())\n            }\n        }\n\n        private val proxyType: Int\n            get() = when (cgTypes.checkedChipId) {\n                R.id.chip_direct -> HProxySelector.TYPE_DIRECT\n                R.id.chip_system_proxy -> HProxySelector.TYPE_SYSTEM\n                R.id.chip_http -> HProxySelector.TYPE_HTTP\n                R.id.chip_socks -> HProxySelector.TYPE_SOCKS\n                else -> HProxySelector.TYPE_DIRECT\n            }\n\n        private fun checkValid(ip: String, port: Int): Boolean {\n            return when (proxyType) {\n                HProxySelector.TYPE_DIRECT, HProxySelector.TYPE_SYSTEM -> true\n                HProxySelector.TYPE_HTTP, HProxySelector.TYPE_SOCKS -> {\n                    HProxySelector.validateIp(ip) && HProxySelector.validatePort(port)\n                }\n\n                else -> false\n            }\n        }\n\n        private fun enableView(@IdRes checkedId: Int) {\n            when (checkedId) {\n                R.id.chip_direct -> {\n                    etIp.isEnabled = false\n                    etPort.isEnabled = false\n                    etIp.text = null\n                    etPort.text = null\n                }\n\n                R.id.chip_system_proxy -> {\n                    etIp.isEnabled = false\n                    etPort.isEnabled = false\n                    etIp.text = null\n                    etPort.text = null\n                }\n\n                R.id.chip_http -> {\n                    etIp.isEnabled = true\n                    etPort.isEnabled = true\n                }\n\n                R.id.chip_socks -> {\n                    etIp.isEnabled = true\n                    etPort.isEnabled = true\n                }\n            }\n        }\n\n        fun show() {\n            initView()\n            dialog.showWithBlurEffect()\n        }\n    }\n\n    override fun SettingsActivity.setupToolbar() {\n        supportActionBar!!.setTitle(R.string.network_settings)\n    }\n}\n"
  },
  {
    "path": "app/src/main/java/com/yenaly/han1meviewer/ui/fragment/settings/PlayerSettingsFragment.kt",
    "content": "package com.yenaly.han1meviewer.ui.fragment.settings\n\nimport android.os.Bundle\nimport androidx.annotation.IntRange\nimport androidx.preference.SeekBarPreference\nimport androidx.preference.SwitchPreferenceCompat\nimport com.yenaly.han1meviewer.R\nimport com.yenaly.han1meviewer.ui.activity.SettingsActivity\nimport com.yenaly.han1meviewer.ui.fragment.IToolbarFragment\nimport com.yenaly.han1meviewer.ui.view.pref.MaterialDialogPreference\nimport com.yenaly.han1meviewer.ui.view.video.HJzvdStd\nimport com.yenaly.han1meviewer.ui.view.video.HMediaKernel\nimport com.yenaly.han1meviewer.util.setSummaryConverter\nimport com.yenaly.yenaly_libs.base.settings.YenalySettingsFragment\nimport com.yenaly.yenaly_libs.utils.toStringArray\n\n/**\n * @project Han1meViewer\n * @author Yenaly Liew\n * @time 2023/09/04 004 16:28\n */\nclass PlayerSettingsFragment : YenalySettingsFragment(R.xml.settings_player),\n    IToolbarFragment<SettingsActivity> {\n\n    companion object {\n        const val SWITCH_PLAYER_KERNEL = \"switch_player_kernel\"\n        const val SHOW_BOTTOM_PROGRESS = \"show_bottom_progress\"\n        const val PLAYER_SPEED = \"player_speed\"\n        const val SLIDE_SENSITIVITY = \"slide_sensitivity\"\n        const val LONG_PRESS_SPEED_TIMES = \"long_press_speed_times\"\n    }\n\n    private val switchPlayerKernel\n            by safePreference<MaterialDialogPreference>(SWITCH_PLAYER_KERNEL)\n    private val showBottomProgressPref\n            by safePreference<SwitchPreferenceCompat>(SHOW_BOTTOM_PROGRESS)\n    private val playerSpeed\n            by safePreference<MaterialDialogPreference>(PLAYER_SPEED)\n    private val slideSensitivity\n            by safePreference<SeekBarPreference>(SLIDE_SENSITIVITY)\n    private val longPressSpeedTimesPref\n            by safePreference<MaterialDialogPreference>(LONG_PRESS_SPEED_TIMES)\n\n    override fun onStart() {\n        super.onStart()\n        (activity as SettingsActivity).setupToolbar()\n    }\n\n    override fun onPreferencesCreated(savedInstanceState: Bundle?) {\n        switchPlayerKernel.apply {\n            val kernelNames = HMediaKernel.Type.entries.map { it.name }.toTypedArray()\n            entries = kernelNames\n            entryValues = kernelNames\n            if (value == null) setValueIndex(HMediaKernel.Type.ExoPlayer.ordinal)\n        }\n        playerSpeed.apply {\n            entries = HJzvdStd.speedStringArray\n            entryValues = HJzvdStd.speedArray.toStringArray()\n            // 不能直接用 defaultValue 设置，没效果\n            if (value == null) setValueIndex(HJzvdStd.DEF_SPEED_INDEX)\n        }\n        slideSensitivity.apply {\n            setSummaryConverter(\n                defValue = HJzvdStd.DEF_PROGRESS_SLIDE_SENSITIVITY,\n                converter = ::toPrettySensitivityString\n            )\n        }\n        longPressSpeedTimesPref.apply {\n            entries = arrayOf(\n                getString(R.string.d_speed_times, 1f),\n                getString(R.string.d_speed_times, 1.5f),\n                getString(R.string.d_speed_times, 2f),\n                \"${getString(R.string.d_speed_times, 2.5f)} (${getString(R.string.default_)})\",\n                getString(R.string.d_speed_times, 2.8f),\n                getString(R.string.d_speed_times, 3f),\n                getString(R.string.d_speed_times, 3.2f),\n                getString(R.string.d_speed_times, 3.5f),\n                getString(R.string.d_speed_times, 3.8f),\n                getString(R.string.d_speed_times, 4f)\n            )\n            entryValues = arrayOf(\n                \"1\", \"1.5\", \"2\", \"2.5\", \"2.8\",\n                \"3\", \"3.2\", \"3.5\", \"3.8\", \"4\"\n            )\n            if (value == null) setValueIndex(3)\n        }\n    }\n\n    /**\n     * 將數字靈敏度轉換為漂亮的字串\n     */\n    private fun toPrettySensitivityString(@IntRange(from = 1, to = 9) value: Int): String {\n        val pretty = when (value) {\n            1, 2 -> getString(R.string.high)\n            3, 4 -> getString(R.string.moderately_high)\n            5 -> getString(R.string.moderate)\n            6 -> getString(R.string.slightly_low)\n            7 -> getString(R.string.low)\n            8 -> getString(R.string.very_low)\n            9 -> getString(R.string.extremely_low)\n            else -> throw IllegalStateException(\"Invalid sensitivity value: $this\")\n        }\n        return getString(R.string.current_slide_sensitivity, pretty)\n    }\n\n    override fun SettingsActivity.setupToolbar() {\n        supportActionBar!!.setTitle(R.string.player_settings)\n    }\n}"
  },
  {
    "path": "app/src/main/java/com/yenaly/han1meviewer/ui/fragment/settings/SharedHKeyframesFragment.kt",
    "content": "package com.yenaly.han1meviewer.ui.fragment.settings\n\nimport android.os.Bundle\nimport android.view.LayoutInflater\nimport android.view.ViewGroup\nimport androidx.fragment.app.activityViewModels\nimport androidx.lifecycle.lifecycleScope\nimport androidx.recyclerview.widget.LinearLayoutManager\nimport androidx.recyclerview.widget.RecyclerView\nimport androidx.recyclerview.widget.RecyclerView.OnScrollListener\nimport com.yenaly.han1meviewer.R\nimport com.yenaly.han1meviewer.databinding.FragmentHKeyframesBinding\nimport com.yenaly.han1meviewer.logic.entity.HKeyframeType\nimport com.yenaly.han1meviewer.ui.StateLayoutMixin\nimport com.yenaly.han1meviewer.ui.activity.SettingsActivity\nimport com.yenaly.han1meviewer.ui.adapter.SharedHKeyframesRvAdapter\nimport com.yenaly.han1meviewer.ui.fragment.IToolbarFragment\nimport com.yenaly.han1meviewer.ui.view.LinearSmoothToStartScroller\nimport com.yenaly.han1meviewer.ui.viewmodel.SettingsViewModel\nimport com.yenaly.han1meviewer.util.setStateViewLayout\nimport com.yenaly.yenaly_libs.base.YenalyFragment\nimport com.yenaly.yenaly_libs.utils.unsafeLazy\nimport kotlinx.coroutines.launch\n\n/**\n * @project Han1meViewer\n * @author Yenaly Liew\n * @time 2023/11/18 018 17:38\n */\nclass SharedHKeyframesFragment : YenalyFragment<FragmentHKeyframesBinding>(),\n    IToolbarFragment<SettingsActivity>, StateLayoutMixin {\n\n    val viewModel by activityViewModels<SettingsViewModel>()\n\n    private val adapter by unsafeLazy { SharedHKeyframesRvAdapter() }\n\n    override fun onStart() {\n        super.onStart()\n        (activity as SettingsActivity).setupToolbar()\n    }\n\n    override fun getViewBinding(\n        inflater: LayoutInflater,\n        container: ViewGroup?\n    ): FragmentHKeyframesBinding {\n        return FragmentHKeyframesBinding.inflate(inflater, container, false)\n    }\n\n    override fun initData(savedInstanceState: Bundle?) {\n        val layoutManager = LinearLayoutManager(context)\n        val smoothScroller = LinearSmoothToStartScroller(context)\n        binding.rvKeyframe.layoutManager = layoutManager\n        binding.rvKeyframe.adapter = adapter\n        binding.btnUp.setOnClickListener {\n            var firstVisibleItemPosition = layoutManager.findFirstVisibleItemPosition()\n            if (adapter.getItemViewType(firstVisibleItemPosition) == HKeyframeType.HEADER) {\n                firstVisibleItemPosition--\n            }\n            for (i in firstVisibleItemPosition downTo 0) {\n                if (adapter.getItemViewType(i) == HKeyframeType.HEADER) {\n                    smoothScroller.targetPosition = i\n                    layoutManager.startSmoothScroll(smoothScroller)\n                    break\n                }\n            }\n        }\n        binding.btnDown.setOnClickListener {\n            var firstVisibleItemPosition = layoutManager.findFirstVisibleItemPosition()\n            if (adapter.getItemViewType(firstVisibleItemPosition) == HKeyframeType.HEADER) {\n                firstVisibleItemPosition++\n            }\n            for (i in firstVisibleItemPosition..<adapter.itemCount) {\n                if (adapter.getItemViewType(i) == HKeyframeType.HEADER) {\n                    smoothScroller.targetPosition = i\n                    layoutManager.startSmoothScroll(smoothScroller)\n                    break\n                }\n            }\n        }\n        binding.rvKeyframe.addOnScrollListener(object : OnScrollListener() {\n            override fun onScrolled(recyclerView: RecyclerView, dx: Int, dy: Int) {\n                binding.btnUp.isEnabled = recyclerView.canScrollVertically(-1)\n                binding.btnDown.isEnabled = recyclerView.canScrollVertically(1)\n            }\n        })\n        adapter.setStateViewLayout(R.layout.layout_empty_view, getString(R.string.here_is_empty))\n    }\n\n    override fun bindDataObservers() {\n        viewLifecycleOwner.lifecycleScope.launch {\n            viewModel.loadAllSharedHKeyframes().collect {\n                adapter.submitList(it)\n            }\n        }\n    }\n\n    override fun SettingsActivity.setupToolbar() {\n        supportActionBar!!.setTitle(R.string.shared_h_keyframe_manage)\n    }\n}"
  },
  {
    "path": "app/src/main/java/com/yenaly/han1meviewer/ui/fragment/video/ChildCommentPopupFragment.kt",
    "content": "package com.yenaly.han1meviewer.ui.fragment.video\n\nimport android.app.Dialog\nimport android.os.Bundle\nimport android.view.Gravity\nimport android.view.LayoutInflater\nimport androidx.annotation.OptIn\nimport androidx.fragment.app.viewModels\nimport androidx.lifecycle.lifecycleScope\nimport androidx.recyclerview.widget.LinearLayoutManager\nimport com.google.android.material.badge.BadgeDrawable\nimport com.google.android.material.badge.BadgeUtils\nimport com.google.android.material.badge.ExperimentalBadgeUtils\nimport com.yenaly.han1meviewer.COMMENT_ID\nimport com.yenaly.han1meviewer.R\nimport com.yenaly.han1meviewer.databinding.PopUpFragmentChildCommentBinding\nimport com.yenaly.han1meviewer.logic.state.WebsiteState\nimport com.yenaly.han1meviewer.ui.adapter.VideoCommentRvAdapter\nimport com.yenaly.han1meviewer.ui.viewmodel.CommentViewModel\nimport com.yenaly.han1meviewer.util.setGravity\nimport com.yenaly.yenaly_libs.base.YenalyBottomSheetDialogFragment\nimport com.yenaly.yenaly_libs.utils.appScreenHeight\nimport com.yenaly.yenaly_libs.utils.arguments\nimport com.yenaly.yenaly_libs.utils.dp\nimport com.yenaly.yenaly_libs.utils.showShortToast\nimport com.yenaly.yenaly_libs.utils.unsafeLazy\nimport kotlinx.coroutines.flow.collectLatest\nimport kotlinx.coroutines.launch\n\n/**\n * @project Hanime1\n * @author Yenaly Liew\n * @time 2022/06/21 021 22:58\n */\nclass ChildCommentPopupFragment :\n    YenalyBottomSheetDialogFragment<PopUpFragmentChildCommentBinding>() {\n\n    val commentId by arguments<String>(COMMENT_ID)\n    val viewModel by viewModels<CommentViewModel>()\n    private val replyAdapter by unsafeLazy {\n        VideoCommentRvAdapter(this)\n    }\n\n    override fun getViewBinding(layoutInflater: LayoutInflater) =\n        PopUpFragmentChildCommentBinding.inflate(layoutInflater)\n\n    override fun initData(savedInstanceState: Bundle?, dialog: Dialog) {\n        if (commentId == null) dialog.dismiss()\n\n        binding.root.minimumHeight = appScreenHeight / 2\n        binding.rvReply.layoutManager = LinearLayoutManager(context)\n        binding.rvReply.adapter = replyAdapter\n\n        viewModel.getCommentReply(commentId!!)\n\n        lifecycleScope.launch {\n            viewModel.videoReplyStateFlow.collect { state ->\n                when (state) {\n                    is WebsiteState.Error -> {\n                        showShortToast(R.string.load_reply_failed)\n                        dialog.dismiss()\n                    }\n\n                    is WebsiteState.Loading -> Unit\n\n                    is WebsiteState.Success -> Unit\n                }\n            }\n        }\n\n        lifecycleScope.launch {\n            viewModel.videoReplyFlow.collectLatest { list ->\n                replyAdapter.submitList(list)\n                attachRedDotCount(list.size)\n            }\n        }\n\n        lifecycleScope.launch {\n            viewModel.postReplyFlow.collect { state ->\n                when (state) {\n                    is WebsiteState.Error -> {\n                        showShortToast(R.string.send_failed)\n                    }\n\n                    is WebsiteState.Loading -> {\n                        showShortToast(R.string.sending_reply)\n                    }\n\n                    is WebsiteState.Success -> {\n                        showShortToast(R.string.send_success)\n                        viewModel.getCommentReply(commentId!!)\n                        replyAdapter.replyPopup?.dismiss()\n                    }\n                }\n            }\n        }\n\n        lifecycleScope.launch {\n            viewModel.commentLikeFlow.collect { state ->\n                when (state) {\n                    is WebsiteState.Error -> showShortToast(state.throwable.message)\n                    is WebsiteState.Loading -> Unit\n                    is WebsiteState.Success -> {\n                        viewModel.handleCommentLike(state.info)\n                        replyAdapter.notifyItemChanged(state.info.commentPosition, 0)\n                    }\n                }\n            }\n        }\n    }\n\n    @OptIn(ExperimentalBadgeUtils::class)\n    private fun attachRedDotCount(count: Int) {\n        val badgeDrawable = BadgeDrawable.create(requireContext())\n        badgeDrawable.isVisible = count > 0\n        badgeDrawable.number = count\n        BadgeUtils.attachBadgeDrawable(badgeDrawable, binding.tvChildComment)\n        badgeDrawable.setGravity(binding.tvChildComment, Gravity.END, 8.dp)\n    }\n}"
  },
  {
    "path": "app/src/main/java/com/yenaly/han1meviewer/ui/fragment/video/CommentFragment.kt",
    "content": "package com.yenaly.han1meviewer.ui.fragment.video\n\nimport android.os.Bundle\nimport android.view.LayoutInflater\nimport android.view.ViewGroup\nimport android.widget.TextView\nimport androidx.core.view.ViewCompat\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.lifecycle.Lifecycle\nimport androidx.lifecycle.lifecycleScope\nimport androidx.lifecycle.repeatOnLifecycle\nimport androidx.recyclerview.widget.LinearLayoutManager\nimport com.lxj.xpopup.XPopup\nimport com.lxj.xpopup.core.BasePopupView\nimport com.lxj.xpopup.interfaces.SimpleCallback\nimport com.yenaly.han1meviewer.COMMENT_TYPE\nimport com.yenaly.han1meviewer.Preferences.isAlreadyLogin\nimport com.yenaly.han1meviewer.R\nimport com.yenaly.han1meviewer.VIDEO_COMMENT_PREFIX\nimport com.yenaly.han1meviewer.databinding.FragmentCommentBinding\nimport com.yenaly.han1meviewer.logic.state.WebsiteState\nimport com.yenaly.han1meviewer.ui.StateLayoutMixin\nimport com.yenaly.han1meviewer.ui.activity.PreviewCommentActivity\nimport com.yenaly.han1meviewer.ui.activity.VideoActivity\nimport com.yenaly.han1meviewer.ui.adapter.VideoCommentRvAdapter\nimport com.yenaly.han1meviewer.ui.popup.ReplyPopup\nimport com.yenaly.han1meviewer.ui.viewmodel.CommentViewModel\nimport com.yenaly.han1meviewer.ui.viewmodel.PreviewCommentPrefetcher\nimport com.yenaly.yenaly_libs.base.YenalyFragment\nimport com.yenaly.yenaly_libs.utils.arguments\nimport com.yenaly.yenaly_libs.utils.showShortToast\nimport com.yenaly.yenaly_libs.utils.unsafeLazy\nimport kotlinx.coroutines.flow.collectLatest\nimport kotlinx.coroutines.launch\n\n/**\n * @project Hanime1\n * @author Yenaly Liew\n * @time 2022/06/18 018 21:09\n */\nclass CommentFragment : YenalyFragment<FragmentCommentBinding>(), StateLayoutMixin {\n\n    val viewModel by activityViewModels<CommentViewModel>()\n\n    private val commentTypePrefix by arguments(COMMENT_TYPE, VIDEO_COMMENT_PREFIX)\n    private val commentAdapter by unsafeLazy {\n        VideoCommentRvAdapter(this)\n    }\n    private val replyPopup by unsafeLazy {\n        ReplyPopup(requireContext()).also { it.hint = getString(R.string.comment) }\n    }\n\n    /**\n     * 是否已经预加载了预览评论\n     */\n    private var isPreviewCommentPrefetched = false\n\n    override fun getViewBinding(\n        inflater: LayoutInflater,\n        container: ViewGroup?\n    ): FragmentCommentBinding {\n        return FragmentCommentBinding.inflate(inflater, container, false)\n    }\n\n    override fun initData(savedInstanceState: Bundle?) {\n        binding.state.init {\n            onEmpty {\n                findViewById<TextView>(R.id.tv_empty).setText(R.string.comment_not_found)\n            }\n        }\n\n        binding.rvComment.layoutManager = LinearLayoutManager(context)\n        binding.rvComment.adapter = commentAdapter\n        binding.rvComment.clipToPadding = false\n\n        if (context is PreviewCommentActivity) {\n            val comments = PreviewCommentPrefetcher.here().commentFlow.value\n            if (comments.isNotEmpty()) {\n                isPreviewCommentPrefetched = true\n                commentAdapter.submitList(comments)\n            }\n        }\n        ViewCompat.setOnApplyWindowInsetsListener(binding.rvComment) { v, insets ->\n            val navBar = insets.getInsets(WindowInsetsCompat.Type.navigationBars())\n            v.updatePadding(bottom = navBar.bottom)\n            WindowInsetsCompat.CONSUMED\n        }\n        binding.srlComment.setOnRefreshListener {\n            viewModel.getComment(commentTypePrefix, viewModel.code)\n        }\n        binding.btnComment.isVisible = isAlreadyLogin\n        replyPopup.setOnSendListener {\n            viewModel.currentUserId?.let { id ->\n                viewModel.postComment(\n                    id,\n                    viewModel.code, commentTypePrefix, replyPopup.comment\n                )\n            } ?: showShortToast(R.string.there_is_a_small_issue)\n        }\n        binding.btnComment.setOnClickListener {\n            XPopup.Builder(context).autoOpenSoftInput(true)\n                .setPopupCallback(object : SimpleCallback() {\n                    override fun beforeShow(popupView: BasePopupView?) {\n                        binding.btnComment.hide()\n                    }\n\n                    override fun onDismiss(popupView: BasePopupView?) {\n                        binding.btnComment.show()\n                    }\n                }).asCustom(replyPopup).show()\n        }\n    }\n\n    override fun bindDataObservers() {\n        lifecycleScope.launch {\n            repeatOnLifecycle(Lifecycle.State.CREATED) {\n                viewModel.videoCommentStateFlow.collect { state ->\n                    binding.rvComment.isGone = state is WebsiteState.Error\n                    when (state) {\n                        is WebsiteState.Error -> {\n                            binding.srlComment.finishRefresh()\n                            binding.state.showError(state.throwable)\n                        }\n\n                        is WebsiteState.Loading -> {\n                            if (!isPreviewCommentPrefetched) {\n                                binding.srlComment.autoRefresh()\n                            }\n                        }\n\n                        is WebsiteState.Success -> {\n                            binding.srlComment.finishRefresh()\n                            viewModel.currentUserId = state.info.currentUserId\n                            showRedDotCount(state.info.videoComment.size)\n                            binding.rvComment.isGone = state.info.videoComment.isEmpty()\n                            if (state.info.videoComment.isEmpty()) {\n                                binding.state.showEmpty()\n                            } else {\n                                binding.state.showContent()\n                            }\n                        }\n                    }\n                }\n            }\n        }\n\n        lifecycleScope.launch {\n            repeatOnLifecycle(Lifecycle.State.CREATED) {\n                viewModel.videoCommentFlow.collectLatest { list ->\n                    if (!isPreviewCommentPrefetched) {\n                        commentAdapter.submitList(list)\n                        if (context is PreviewCommentActivity) {\n                            PreviewCommentPrefetcher.here().update(list)\n                        }\n                    }\n                }\n            }\n        }\n\n        lifecycleScope.launch {\n            viewModel.postCommentFlow.collect { state ->\n                when (state) {\n                    is WebsiteState.Error -> {\n                        showShortToast(R.string.send_failed)\n                    }\n\n                    is WebsiteState.Loading -> {\n                        showShortToast(R.string.sending_comment)\n                    }\n\n                    is WebsiteState.Success -> {\n                        showShortToast(R.string.send_success)\n                        viewModel.getComment(commentTypePrefix, viewModel.code)\n                        replyPopup.dismiss()\n                    }\n                }\n            }\n        }\n\n        lifecycleScope.launch {\n            viewModel.postReplyFlow.collect { state ->\n                when (state) {\n                    is WebsiteState.Error -> {\n                        showShortToast(R.string.send_failed)\n                    }\n\n                    is WebsiteState.Loading -> {\n                        showShortToast(R.string.sending_reply)\n                    }\n\n                    is WebsiteState.Success -> {\n                        showShortToast(R.string.send_success)\n                        viewModel.getComment(commentTypePrefix, viewModel.code)\n                        commentAdapter.replyPopup?.dismiss()\n                    }\n                }\n            }\n        }\n\n        lifecycleScope.launch {\n            viewModel.commentLikeFlow.collect { state ->\n                when (state) {\n                    is WebsiteState.Error -> showShortToast(state.throwable.message)\n                    is WebsiteState.Loading -> Unit\n                    is WebsiteState.Success -> {\n                        viewModel.handleCommentLike(state.info)\n                    }\n                }\n            }\n        }\n    }\n\n    private fun showRedDotCount(count: Int) {\n        val parent = context as? VideoActivity\n        parent?.showRedDotCount(count)\n    }\n}"
  },
  {
    "path": "app/src/main/java/com/yenaly/han1meviewer/ui/fragment/video/VideoIntroductionFragment.kt",
    "content": "package com.yenaly.han1meviewer.ui.fragment.video\n\nimport android.app.Activity\nimport android.content.Context\nimport android.graphics.Typeface\nimport android.os.Bundle\nimport android.view.LayoutInflater\nimport android.view.MotionEvent\nimport android.view.ViewGroup\nimport androidx.core.view.ViewCompat\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.lifecycle.Lifecycle\nimport androidx.lifecycle.lifecycleScope\nimport androidx.lifecycle.repeatOnLifecycle\nimport androidx.recyclerview.widget.ConcatAdapter\nimport androidx.recyclerview.widget.DiffUtil\nimport androidx.recyclerview.widget.GridLayoutManager\nimport androidx.recyclerview.widget.LinearLayoutManager\nimport androidx.recyclerview.widget.RecyclerView\nimport androidx.recyclerview.widget.RecyclerView.OnItemTouchListener\nimport androidx.viewpager2.widget.ViewPager2\nimport coil.load\nimport coil.transform.CircleCropTransformation\nimport com.chad.library.adapter4.viewholder.DataBindingHolder\nimport com.ctetin.expandabletextviewlibrary.ExpandableTextView\nimport com.ctetin.expandabletextviewlibrary.app.LinkType\nimport com.itxca.spannablex.spannable\nimport com.lxj.xpopup.XPopup\nimport com.yenaly.han1meviewer.ADVANCED_SEARCH_MAP\nimport com.yenaly.han1meviewer.HAdvancedSearch\nimport com.yenaly.han1meviewer.HCacheManager\nimport com.yenaly.han1meviewer.HanimeResolution\nimport com.yenaly.han1meviewer.LOCAL_DATE_FORMAT\nimport com.yenaly.han1meviewer.Preferences\nimport com.yenaly.han1meviewer.Preferences.isAlreadyLogin\nimport com.yenaly.han1meviewer.R\nimport com.yenaly.han1meviewer.VIDEO_LAYOUT_MATCH_PARENT\nimport com.yenaly.han1meviewer.VIDEO_LAYOUT_WRAP_CONTENT\nimport com.yenaly.han1meviewer.VideoCoverSize\nimport com.yenaly.han1meviewer.advancedSearchMapOf\nimport com.yenaly.han1meviewer.databinding.FragmentVideoIntroductionBinding\nimport com.yenaly.han1meviewer.databinding.ItemVideoIntroductionBinding\nimport com.yenaly.han1meviewer.getHanimeShareText\nimport com.yenaly.han1meviewer.getHanimeVideoDownloadLink\nimport com.yenaly.han1meviewer.getHanimeVideoLink\nimport com.yenaly.han1meviewer.logic.model.HanimeInfo\nimport com.yenaly.han1meviewer.logic.model.HanimeVideo\nimport com.yenaly.han1meviewer.logic.state.VideoLoadingState\nimport com.yenaly.han1meviewer.logic.state.WebsiteState\nimport com.yenaly.han1meviewer.ui.activity.SearchActivity\nimport com.yenaly.han1meviewer.ui.adapter.AdapterLikeDataBindingPage\nimport com.yenaly.han1meviewer.ui.adapter.BaseSingleDifferAdapter\nimport com.yenaly.han1meviewer.ui.adapter.HanimeVideoRvAdapter\nimport com.yenaly.han1meviewer.ui.adapter.RvWrapper.Companion.wrappedWith\nimport com.yenaly.han1meviewer.ui.adapter.VideoColumnTitleAdapter\nimport com.yenaly.han1meviewer.ui.viewmodel.VideoViewModel\nimport com.yenaly.han1meviewer.util.requestExternalStoragePermission\nimport com.yenaly.han1meviewer.util.requestPostNotificationPermission\nimport com.yenaly.han1meviewer.util.setDrawableTop\nimport com.yenaly.han1meviewer.util.showAlertDialog\nimport com.yenaly.han1meviewer.worker.HanimeDownloadManagerV2\nimport com.yenaly.han1meviewer.worker.HanimeDownloadWorker\nimport com.yenaly.yenaly_libs.base.YenalyFragment\nimport com.yenaly.yenaly_libs.utils.browse\nimport com.yenaly.yenaly_libs.utils.copyToClipboard\nimport com.yenaly.yenaly_libs.utils.shareText\nimport com.yenaly.yenaly_libs.utils.showShortToast\nimport com.yenaly.yenaly_libs.utils.startActivity\nimport com.yenaly.yenaly_libs.utils.unsafeLazy\nimport com.yenaly.yenaly_libs.utils.view.clickTrigger\nimport com.yenaly.yenaly_libs.utils.view.clickWithCondition\nimport com.yenaly.yenaly_libs.utils.view.findParent\nimport kotlinx.coroutines.launch\nimport kotlinx.coroutines.sync.Mutex\nimport kotlinx.coroutines.sync.withLock\nimport kotlinx.datetime.format\n\n/**\n * @project Hanime1\n * @author Yenaly Liew\n * @time 2022/06/18 018 21:09\n */\nclass VideoIntroductionFragment : YenalyFragment<FragmentVideoIntroductionBinding>() {\n\n    companion object {\n        private const val FAV = 1\n        private const val PLAYLIST = 1 shl 1\n        private const val SUBSCRIBE = 1 shl 2\n\n        val COMPARATOR = object : DiffUtil.ItemCallback<HanimeVideo>() {\n            override fun areItemsTheSame(oldItem: HanimeVideo, newItem: HanimeVideo): Boolean {\n                return true\n            }\n\n            override fun areContentsTheSame(oldItem: HanimeVideo, newItem: HanimeVideo): Boolean {\n                return false\n            }\n\n            override fun getChangePayload(oldItem: HanimeVideo, newItem: HanimeVideo): Any {\n                var bitset = 0\n                if (oldItem.isFav != newItem.isFav)\n                    bitset = bitset or FAV\n                if (!(oldItem.myList?.isSelectedArray contentEquals newItem.myList?.isSelectedArray))\n                    bitset = bitset or PLAYLIST\n                if (oldItem.artist?.isSubscribed != newItem.artist?.isSubscribed)\n                    bitset = bitset or SUBSCRIBE\n                return bitset\n            }\n        }\n    }\n\n    val viewModel by activityViewModels<VideoViewModel>()\n\n    private var checkedQuality: String? = null\n\n    private val videoIntroAdapter = VideoIntroductionAdapter()\n    private val playlistTitleAdapter =\n        VideoColumnTitleAdapter(title = R.string.series_video, notifyWhenSet = true)\n    private val playlistAdapter = HanimeVideoRvAdapter(VIDEO_LAYOUT_WRAP_CONTENT)\n    private val playlistWrapper = playlistAdapter.wrappedWith {\n        LinearLayoutManager(context, LinearLayoutManager.HORIZONTAL, false)\n    }\n    private val relatedTitleAdapter =\n        VideoColumnTitleAdapter(title = R.string.related_video)\n    private val relatedAdapter = HanimeVideoRvAdapter(VIDEO_LAYOUT_MATCH_PARENT)\n\n    private var multi = ConcatAdapter()\n\n    private val layoutManager by unsafeLazy {\n        GridLayoutManager(context, 1).apply {\n            spanSizeLookup = object : GridLayoutManager.SpanSizeLookup() {\n                override fun getSpanSize(position: Int): Int {\n                    if (multi.getWrappedAdapterAndPosition(position).first === relatedAdapter) {\n                        return 1\n                    }\n                    return spanCount\n                }\n            }\n        }\n    }\n\n    /**\n     * 保证 submitList 不同时调用\n     */\n    private val mutex = Mutex()\n\n    override fun getViewBinding(\n        inflater: LayoutInflater,\n        container: ViewGroup?,\n    ): FragmentVideoIntroductionBinding {\n        return FragmentVideoIntroductionBinding.inflate(inflater, container, false)\n    }\n\n    override fun initData(savedInstanceState: Bundle?) {\n        binding.rvVideoIntro.layoutManager = layoutManager\n        binding.rvVideoIntro.adapter = multi\n        binding.rvVideoIntro.addOnItemTouchListener(VideoIntroTouchListener())\n        binding.rvVideoIntro.clipToPadding = false\n        ViewCompat.setOnApplyWindowInsetsListener(binding.rvVideoIntro) { v, insets ->\n            val navBar = insets.getInsets(WindowInsetsCompat.Type.navigationBars())\n            v.updatePadding(bottom = navBar.bottom)\n            WindowInsetsCompat.CONSUMED\n        }\n    }\n\n    override fun bindDataObservers() {\n        viewLifecycleOwner.lifecycleScope.launch {\n            repeatOnLifecycle(Lifecycle.State.CREATED) {\n                viewModel.hanimeVideoStateFlow.collect { state ->\n                    binding.rvVideoIntro.isVisible = state is VideoLoadingState.Success\n                    when (state) {\n                        is VideoLoadingState.Error -> Unit\n\n                        is VideoLoadingState.Loading -> Unit\n\n                        is VideoLoadingState.Success -> {\n                            val video = state.info\n                            // 重新赋值，防止重复添加\n                            multi = ConcatAdapter()\n                            binding.rvVideoIntro.adapter = multi\n\n                            mutex.withLock {\n                                videoIntroAdapter.submit(video) // 挂起是为了让它在首位\n                            }\n                            multi.addAdapter(videoIntroAdapter)\n                            if (video.playlist != null && !viewModel.fromDownload) {\n                                playlistTitleAdapter.subtitle = video.playlist.playlistName\n                                multi.addAdapter(playlistTitleAdapter)\n                                playlistAdapter.submitList(video.playlist.video)\n                                multi.addAdapter(playlistWrapper)\n                            } else {\n                                multi.removeAdapter(playlistTitleAdapter)\n                                multi.removeAdapter(playlistWrapper)\n                            }\n                            if (!viewModel.fromDownload) {\n                                multi.addAdapter(relatedTitleAdapter)\n                                relatedAdapter.submitList(video.relatedHanimes)\n                                multi.addAdapter(relatedAdapter)\n                                layoutManager.spanCount = video.relatedHanimes.eachGridCounts\n                            }\n                        }\n\n                        is VideoLoadingState.NoContent -> Unit\n                    }\n                }\n            }\n        }\n\n        viewLifecycleOwner.lifecycleScope.launch {\n            repeatOnLifecycle(Lifecycle.State.CREATED) {\n                viewModel.hanimeVideoFlow.collect { video ->\n                    mutex.withLock {\n                        videoIntroAdapter.submit(video)\n                    }\n                }\n            }\n        }\n\n        viewLifecycleOwner.lifecycleScope.launch {\n            viewModel.addToFavVideoFlow.collect { state ->\n                videoIntroAdapter.binding?.btnAddToFav?.setTag(\n                    R.id.click_condition, state != WebsiteState.Loading\n                )\n                when (state) {\n                    is WebsiteState.Error -> {\n                        showShortToast(R.string.fav_failed)\n                    }\n\n                    is WebsiteState.Loading -> Unit\n                    is WebsiteState.Success -> {\n                        val isFav = state.info\n                        if (isFav) {\n                            showShortToast(R.string.cancel_fav)\n                        } else {\n                            showShortToast(R.string.add_to_fav)\n                        }\n                    }\n                }\n            }\n        }\n\n        viewLifecycleOwner.lifecycleScope.launch {\n            viewModel.loadDownloadedFlow.collect { entity ->\n                if (entity == null) { // 没下\n                    viewModel.hanimeVideoFlow.value?.let {\n                        val checkedQuality = requireNotNull(checkedQuality)\n                        notifyDownload(it, oldQuality = null, newQuality = checkedQuality) {\n                            launch {\n                                enqueueDownloadWork(it)\n                            }\n                        }\n                    }\n                } else {\n                    viewModel.hanimeVideoFlow.value?.let {\n                        // #issue-194: 重复下载提示&重新下载\n                        val checkedQuality = requireNotNull(checkedQuality)\n                        notifyDownload(\n                            it, oldQuality = entity.quality, newQuality = checkedQuality\n                        ) {\n                            launch {\n                                enqueueDownloadWork(it, redownload = true)\n                            }\n                        }\n                    }\n                }\n            }\n        }\n\n        viewLifecycleOwner.lifecycleScope.launch {\n            viewModel.modifyMyListFlow.collect { state ->\n                when (state) {\n                    is WebsiteState.Error -> {\n                        showShortToast(R.string.modify_failed)\n                    }\n\n                    is WebsiteState.Loading -> Unit\n                    is WebsiteState.Success -> {\n                        showShortToast(R.string.modify_success)\n                    }\n                }\n            }\n        }\n\n        viewLifecycleOwner.lifecycleScope.launch {\n            viewModel.subscribeArtistFlow.collect { state ->\n                videoIntroAdapter.binding?.btnSubscribe?.setTag(\n                    R.id.click_condition, state != WebsiteState.Loading\n                )\n                when (state) {\n                    is WebsiteState.Error -> {\n                        showShortToast(R.string.subscribe_failed)\n                    }\n\n                    is WebsiteState.Loading -> Unit\n                    is WebsiteState.Success -> {\n                        if (state.info) {\n                            showShortToast(R.string.subscribe_success)\n                        } else {\n                            showShortToast(R.string.unsubscribe_success)\n                        }\n                        activity?.setResult(Activity.RESULT_OK)\n                    }\n                }\n            }\n        }\n    }\n\n    private fun notifyDownload(\n        info: HanimeVideo, oldQuality: String?, newQuality: String,\n        action: () -> Unit\n    ) {\n        val notifyMsg = spannable {\n            getString(R.string.download_video_detail_below).text()\n            newline(2)\n            if (oldQuality != null) {\n                getString(R.string.check_video_exists_in_download, oldQuality).text()\n                newline(2)\n            }\n            getString(R.string.name_with_colon).text()\n            newline()\n            info.title.span {\n                style(Typeface.BOLD)\n            }\n            newline()\n            getString(R.string.quality_with_colon).text()\n            newline()\n            if (oldQuality != null && oldQuality != newQuality) {\n                \"$oldQuality → \".text()\n                newQuality.span {\n                    style(Typeface.BOLD)\n                }\n            } else {\n                newQuality.span {\n                    style(Typeface.BOLD)\n                }\n            }\n            newline(2)\n            getString(R.string.after_download_tips).text()\n        }\n        requireContext().showAlertDialog {\n            setTitle(if (oldQuality != null) R.string.sure_to_redownload else R.string.sure_to_download)\n            setMessage(notifyMsg)\n            setPositiveButton(R.string.sure) { _, _ ->\n                action.invoke()\n            }\n            setNegativeButton(R.string.no, null)\n            setNeutralButton(R.string.go_to_official) { _, _ ->\n                browse(getHanimeVideoDownloadLink(viewModel.videoCode))\n            }\n        }\n    }\n\n    private suspend fun enqueueDownloadWork(videoData: HanimeVideo, redownload: Boolean = false) {\n        requireContext().requestPostNotificationPermission()\n        val checkedQuality = requireNotNull(checkedQuality)\n        HCacheManager.saveHanimeVideoInfo(viewModel.videoCode, videoData)\n        // HanimeDownloadManager.addTask(\n        HanimeDownloadManagerV2.addTask(\n            HanimeDownloadWorker.Args(\n                quality = checkedQuality,\n                downloadUrl = videoData.videoUrls[checkedQuality]?.link,\n                videoType = videoData.videoUrls[checkedQuality]?.suffix,\n                hanimeName = videoData.title,\n                videoCode = viewModel.videoCode,\n                coverUrl = videoData.coverUrl,\n            ),\n            redownload = redownload\n        )\n    }\n\n    private val List<HanimeInfo>.eachGridCounts\n        get() = if (isNotEmpty() && this.first().itemType == HanimeInfo.NORMAL) {\n            VideoCoverSize.Normal.videoInOneLine\n        } else {\n            VideoCoverSize.Simplified.videoInOneLine\n        }\n\n    private inner class VideoIntroTouchListener : OnItemTouchListener {\n\n        private var startX = 0\n        private var vp2: ViewPager2? = null\n        private var isNotHorizontalWrapper = false\n\n        override fun onInterceptTouchEvent(rv: RecyclerView, e: MotionEvent): Boolean {\n            when (e.action) {\n                MotionEvent.ACTION_DOWN -> {\n                    startX = e.x.toInt()\n                    val childView = rv.findChildViewUnder(e.x, e.y)\n                    val position = childView?.let(rv::getChildAdapterPosition) ?: return false\n                    val adapter = multi.getWrappedAdapterAndPosition(position).first\n                    isNotHorizontalWrapper = adapter !== playlistWrapper\n                    val vp2 = vp2 ?: rv.findParent<ViewPager2>().also { vp2 = it }\n                    if (vp2.isUserInputEnabled != isNotHorizontalWrapper) {\n                        vp2.isUserInputEnabled = isNotHorizontalWrapper\n                    }\n                }\n\n                MotionEvent.ACTION_MOVE -> {\n                    if (isNotHorizontalWrapper) return false\n                    val endX = e.x.toInt()\n                    val direction = startX - endX\n                    val canScrollHorizontally =\n                        playlistWrapper.wrapper?.canScrollHorizontally(1)?.not()?.let { csh ->\n                            if (!csh) false else direction > 0\n                        } ?: true\n                    val vp2 = vp2 ?: rv.findParent<ViewPager2>().also { vp2 = it }\n                    if (vp2.isUserInputEnabled != canScrollHorizontally) {\n                        vp2.isUserInputEnabled = canScrollHorizontally\n                    }\n                }\n            }\n            return false\n        }\n\n        override fun onTouchEvent(rv: RecyclerView, e: MotionEvent) = Unit\n\n        override fun onRequestDisallowInterceptTouchEvent(disallowIntercept: Boolean) = Unit\n\n    }\n\n    private inner class VideoIntroductionAdapter :\n        BaseSingleDifferAdapter<HanimeVideo, DataBindingHolder<ItemVideoIntroductionBinding>>(\n            COMPARATOR\n        ), AdapterLikeDataBindingPage<ItemVideoIntroductionBinding> {\n\n        override var binding: ItemVideoIntroductionBinding? = null\n\n        private val onLinkClickListener =\n            ExpandableTextView.OnLinkClickListener { type, content, _ ->\n                when (type) {\n                    LinkType.LINK_TYPE -> {\n                        // #issue-crashlytics-8a65dcf527b961e98d9991352e36a425\n                        try {\n                            content?.let(context::browse)\n                        } catch (_: Exception) {\n                            content?.copyToClipboard()\n                            showShortToast(R.string.copy_to_clipboard)\n                        }\n                    }\n\n                    else -> Unit\n                }\n            }\n\n        override fun onBindViewHolder(\n            holder: DataBindingHolder<ItemVideoIntroductionBinding>,\n            item: HanimeVideo?,\n        ) {\n            item ?: return\n            holder.binding.apply {\n                this@VideoIntroductionAdapter.binding = this\n                uploadTime.text = item.uploadTime?.format(LOCAL_DATE_FORMAT)\n                views.text = if (viewModel.fromDownload) {\n                    getString(R.string.s_view_times, \"0721\")\n                } else {\n                    getString(R.string.s_view_times, item.views.toString())\n                }\n                tvIntroduction.linkClickListener = onLinkClickListener\n                tvIntroduction.setContent(item.introduction)\n                tags.tags = item.tags\n                tags.lifecycle = viewLifecycleOwner.lifecycle\n\n                initTitle(item)\n                initArtist(item.artist)\n                initDownloadButton(item)\n                initFunctionBar(item)\n            }\n        }\n\n        override fun onBindViewHolder(\n            holder: DataBindingHolder<ItemVideoIntroductionBinding>,\n            item: HanimeVideo?,\n            payloads: List<Any>,\n        ) {\n            if (payloads.isEmpty() || payloads.first() == 0)\n                return super.onBindViewHolder(holder, item, payloads)\n            item ?: return\n            val bitset = payloads.first() as Int\n            if (bitset and FAV != 0) {\n                holder.binding.initFavButton(item)\n            }\n            // #issue-202: 加入清单之后不会正常刷新\n            if (bitset and PLAYLIST != 0) {\n                holder.binding.initMyList(item.myList)\n            }\n            if (bitset and SUBSCRIBE != 0) {\n                holder.binding.initArtist(item.artist)\n            }\n        }\n\n        override fun onCreateViewHolder(\n            context: Context,\n            parent: ViewGroup,\n            viewType: Int,\n        ): DataBindingHolder<ItemVideoIntroductionBinding> {\n            return DataBindingHolder(\n                ItemVideoIntroductionBinding.inflate(\n                    LayoutInflater.from(context), parent, false\n                )\n            )\n        }\n\n        private fun ItemVideoIntroductionBinding.initTitle(info: HanimeVideo) {\n            title.text = info.title.also { initShareButton(it) }\n            chineseTitle.text = info.chineseTitle\n            // #issue-80: 长按复制功能请求\n            title.setOnLongClickListener {\n                title.text.copyToClipboard()\n                showShortToast(R.string.copy_to_clipboard)\n                return@setOnLongClickListener true\n            }\n            chineseTitle.setOnLongClickListener {\n                chineseTitle.text.copyToClipboard()\n                showShortToast(R.string.copy_to_clipboard)\n                return@setOnLongClickListener true\n            }\n        }\n\n        private fun ItemVideoIntroductionBinding.initFavButton(info: HanimeVideo) {\n            if (info.isFav) {\n                btnAddToFav.setDrawableTop(R.drawable.ic_baseline_favorite_24)\n                btnAddToFav.setText(R.string.liked)\n            } else {\n                btnAddToFav.setDrawableTop(R.drawable.ic_baseline_favorite_border_24)\n                btnAddToFav.setText(R.string.add_to_fav)\n            }\n            // #issue-204: 收藏可能会导致重复\n            // reason: 1. 在收藏时，可能会多次点击，导致多次请求\n            //         2. payload 后没有重新绑定新 videoData，点击事件未更新\n            btnAddToFav.clickWithCondition(viewLifecycleOwner.lifecycle, R.id.click_condition) {\n                if (isAlreadyLogin) {\n                    it.setTag(R.id.click_condition, false)\n                    if (info.isFav) {\n                        viewModel.removeFromFavVideo(\n                            viewModel.videoCode,\n                            info.currentUserId,\n                        )\n                    } else {\n                        viewModel.addToFavVideo(\n                            viewModel.videoCode,\n                            info.currentUserId,\n                        )\n                    }\n                } else {\n                    showShortToast(R.string.login_first)\n                }\n            }\n        }\n\n        private fun ItemVideoIntroductionBinding.initArtist(artist: HanimeVideo.Artist?) {\n            if (artist == null) {\n                vgArtist.isGone = true\n            } else {\n                vgArtist.isGone = false\n                vgArtist.setOnClickListener {\n                    startActivity<SearchActivity>(\n                        ADVANCED_SEARCH_MAP to advancedSearchMapOf(\n                            HAdvancedSearch.QUERY to artist.name,\n                            HAdvancedSearch.GENRE to artist.genre\n                        )\n                    )\n                }\n                tvArtist.text = artist.name\n                tvGenre.text = artist.genre\n                ivArtist.load(artist.avatarUrl) {\n                    crossfade(true)\n                    transformations(CircleCropTransformation())\n                }\n                btnSubscribe.isVisible = artist.post != null\n                if (btnSubscribe.isVisible && artist.post != null) {\n                    btnSubscribe.text = if (artist.isSubscribed) {\n                        getString(R.string.subscribed)\n                    } else {\n                        getString(R.string.subscribe)\n                    }\n                    btnSubscribe.clickWithCondition(\n                        viewLifecycleOwner.lifecycle, R.id.click_condition\n                    ) {\n                        if (isAlreadyLogin) {\n                            if (artist.isSubscribed) {\n                                context.showAlertDialog {\n                                    setTitle(R.string.unsubscribe_artist)\n                                    setMessage(R.string.sure_to_unsubscribe)\n                                    setPositiveButton(R.string.sure) { _, _ ->\n                                        it.setTag(R.id.click_condition, false)\n                                        viewModel.unsubscribeArtist(\n                                            artist.post.userId,\n                                            artist.post.artistId\n                                        )\n                                    }\n                                    setNegativeButton(R.string.no, null)\n                                }\n                            } else {\n                                it.setTag(R.id.click_condition, false)\n                                viewModel.subscribeArtist(\n                                    artist.post.userId,\n                                    artist.post.artistId\n                                )\n                            }\n                        } else {\n                            showShortToast(R.string.login_first)\n                        }\n                    }\n                }\n            }\n        }\n\n        private fun ItemVideoIntroductionBinding.initFunctionBar(videoData: HanimeVideo) {\n            if (viewModel.fromDownload) {\n                nsvButtons.isGone = true\n            } else {\n                nsvButtons.isVisible = true\n                initFavButton(videoData)\n                initMyList(videoData.myList)\n                btnToWebpage.clickTrigger(viewLifecycleOwner.lifecycle) {\n                    browse(getHanimeVideoLink(viewModel.videoCode))\n                }\n            }\n        }\n\n        private fun ItemVideoIntroductionBinding.initMyList(myList: HanimeVideo.MyList?) {\n            btnMyList.setOnClickListener {\n                if (isAlreadyLogin && myList != null && myList.myListInfo.isNotEmpty()) {\n                    requireContext().showAlertDialog {\n                        setTitle(R.string.add_to_playlist)\n                        setMultiChoiceItems(\n                            myList.titleArray,\n                            myList.isSelectedArray,\n                        ) { _, index, isChecked ->\n                            viewModel.modifyMyList(\n                                myList.myListInfo[index].code,\n                                viewModel.videoCode, isChecked, index\n                            )\n                        }\n                        setNeutralButton(R.string.back, null)\n                    }\n                } else {\n                    showShortToast(R.string.login_first)\n                }\n            }\n        }\n\n        private fun ItemVideoIntroductionBinding.initShareButton(title: String) {\n            val shareText = getHanimeShareText(title, viewModel.videoCode)\n            btnShare.setOnClickListener {\n                shareText(shareText, getString(R.string.long_press_share_to_copy))\n            }\n            btnShare.setOnLongClickListener {\n                shareText.copyToClipboard()\n                showShortToast(R.string.copy_to_clipboard)\n                return@setOnLongClickListener true\n            }\n        }\n\n        private fun ItemVideoIntroductionBinding.initDownloadButton(videoData: HanimeVideo) {\n            if (videoData.videoUrls.isEmpty()) {\n                showShortToast(R.string.no_video_links_found)\n            } else btnDownload.clickTrigger(viewLifecycleOwner.lifecycle) {\n                XPopup.Builder(context)\n                    .atView(it)\n                    .asAttachList(videoData.videoUrls.keys.toTypedArray(), null) { _, key ->\n                        if (key == HanimeResolution.RES_UNKNOWN) {\n                            showShortToast(R.string.cannot_download_here)\n                            browse(getHanimeVideoDownloadLink(viewModel.videoCode))\n                        } else {\n                            checkedQuality = key\n                            lifecycleScope.launch {\n                                if (!Preferences.isPrivateDirectory) {\n                                    context.requestExternalStoragePermission()\n                                }\n                            }\n                            viewModel.findDownloadedHanime(viewModel.videoCode)\n                        }\n                    }.show()\n            }\n        }\n    }\n}"
  },
  {
    "path": "app/src/main/java/com/yenaly/han1meviewer/ui/popup/CoilImageLoader.kt",
    "content": "package com.yenaly.han1meviewer.ui.popup\n\nimport android.content.Context\nimport android.graphics.Matrix\nimport android.view.View\nimport android.widget.ImageView\nimport android.widget.ProgressBar\nimport androidx.annotation.DrawableRes\nimport androidx.core.view.isVisible\nimport coil.annotation.ExperimentalCoilApi\nimport coil.imageLoader\nimport coil.load\nimport coil.request.ErrorResult\nimport coil.request.ImageRequest\nimport coil.request.SuccessResult\nimport com.lxj.xpopup.core.ImageViewerPopupView\nimport com.lxj.xpopup.interfaces.XPopupImageLoader\nimport com.lxj.xpopup.photoview.PhotoView\nimport java.io.File\n\n/**\n * @project Han1meViewer\n * @author Yenaly Liew\n * @time 2022/08/26 026 22:00\n */\nclass CoilImageLoader(@DrawableRes private val errImgRes: Int = 0) : XPopupImageLoader {\n\n    override fun loadSnapshot(uri: Any, snapshot: PhotoView, srcView: ImageView?) {\n        snapshot.load(uri)\n    }\n\n    override fun loadImage(\n        position: Int,\n        uri: Any,\n        popupView: ImageViewerPopupView,\n        snapshot: PhotoView,\n        progressBar: ProgressBar,\n    ): View {\n        progressBar.isVisible = true\n        val photoView = PhotoView(popupView.context).apply {\n            isZoomable = false\n            setOnMatrixChangeListener {\n                val matrix = Matrix()\n                this.getSuppMatrix(matrix)\n                snapshot.setSuppMatrix(matrix)\n            }\n            this.setOnClickListener {\n                popupView.dismiss()\n            }\n            popupView.longPressListener?.let {\n                this.setOnLongClickListener {\n                    popupView.longPressListener.onLongPressed(popupView, position)\n                    return@setOnLongClickListener false\n                }\n            }\n        }\n\n        if (snapshot.drawable != null && (snapshot.tag as Int) == position) {\n            photoView.setImageDrawable(snapshot.drawable.constantState?.newDrawable())\n        }\n        photoView.load(uri) {\n            error(errImgRes)\n            listener(object : ImageRequest.Listener {\n                override fun onError(request: ImageRequest, result: ErrorResult) {\n                    progressBar.isVisible = false\n                    photoView.isZoomable = false\n                }\n\n                override fun onSuccess(request: ImageRequest, result: SuccessResult) {\n                    progressBar.isVisible = false\n                    photoView.isZoomable = true\n                }\n            })\n        }\n\n        return photoView\n    }\n\n    @OptIn(ExperimentalCoilApi::class)\n    override fun getImageFile(context: Context, uri: Any): File? {\n        return context.imageLoader.diskCache?.openSnapshot(uri.toString())?.use { snapshot ->\n            snapshot.data.toFile()\n        }\n    }\n}"
  },
  {
    "path": "app/src/main/java/com/yenaly/han1meviewer/ui/popup/HTimePickerPopup.kt",
    "content": "package com.yenaly.han1meviewer.ui.popup\n\nimport android.content.Context\nimport com.google.android.material.button.MaterialButton\nimport com.lxj.xpopupext.popup.TimePickerPopup\nimport com.yenaly.han1meviewer.R\n\n/**\n * #issue-161: 高级搜索可以选择年或年月\n */\nclass HTimePickerPopup(context: Context) : TimePickerPopup(context) {\n\n    private lateinit var btnSwitch: MaterialButton\n\n    var mode: Mode = Mode.YM\n        private set\n\n    override fun getImplLayoutId(): Int = R.layout.pop_up_ext_h_time_picker\n\n    override fun onCreate() {\n        super.onCreate()\n        btnSwitch = findViewById(R.id.btnSwitch)\n        btnSwitch.text = when (mode) {\n            Mode.YM -> context.getString(R.string.switch_to_year)\n            else -> context.getString(R.string.switch_to_year_month)\n        }\n        btnSwitch.setOnClickListener {\n            when (mode) {\n                Mode.YM -> setMode(Mode.Y)\n                else -> setMode(Mode.YM)\n            }\n            onCreate()\n        }\n    }\n\n    override fun setMode(mode: Mode): TimePickerPopup {\n        this.mode = mode\n        return super.setMode(mode)\n    }\n}"
  },
  {
    "path": "app/src/main/java/com/yenaly/han1meviewer/ui/popup/ReplyPopup.kt",
    "content": "package com.yenaly.han1meviewer.ui.popup\n\nimport android.content.Context\nimport android.widget.EditText\nimport com.google.android.material.button.MaterialButton\nimport com.lxj.xpopup.core.BottomPopupView\nimport com.yenaly.han1meviewer.R\nimport com.yenaly.yenaly_libs.utils.unsafeLazy\n\n/**\n * @project Han1meViewer\n * @author Yenaly Liew\n * @time 2022/09/20 020 09:48\n */\nclass ReplyPopup(context: Context) : BottomPopupView(context) {\n\n    private val editText by unsafeLazy { findViewById<EditText>(R.id.et_comment) }\n    private val btnSend by unsafeLazy { findViewById<MaterialButton>(R.id.btn_send) }\n\n    private var commentPrefix: String? = null\n    private var sendListener: OnClickListener? = null\n\n    override fun getImplLayoutId() = R.layout.pop_up_reply\n\n    override fun onCreate() {\n        super.onCreate()\n        editText.hint = hint\n        commentPrefix?.let(editText::append)\n        sendListener?.let(btnSend::setOnClickListener)\n    }\n\n    /**\n     * 得到你输入的内容\n     */\n    val comment get() = editText.text.toString()\n\n    /**\n     * 设置提示\n     */\n    var hint: CharSequence? = null\n\n    /**\n     * 设置前缀，用于回复子评论\n     *\n     * 例如：@xxx something\n     */\n    fun initCommentPrefix(username: String) {\n        commentPrefix = \"@$username \"\n    }\n\n    /**\n     * 设置发送按钮监听器\n     */\n    fun setOnSendListener(listener: OnClickListener) {\n        this.sendListener = listener\n    }\n}"
  },
  {
    "path": "app/src/main/java/com/yenaly/han1meviewer/ui/view/BlurTransformation.kt",
    "content": "@file:Suppress(\"unused\", \"deprecation\")\n\npackage com.yenaly.han1meviewer.ui.view\n\nimport android.content.Context\nimport android.graphics.Bitmap\nimport android.graphics.Paint\nimport android.renderscript.Allocation\nimport android.renderscript.Element\nimport android.renderscript.RenderScript\nimport android.renderscript.ScriptIntrinsicBlur\nimport androidx.core.graphics.applyCanvas\nimport androidx.core.graphics.createBitmap\nimport coil.size.Size\nimport coil.transform.Transformation\n\n/**\n * A [Transformation] that applies a Gaussian blur to an image.\n *\n * @param context The [Context] used to create a [RenderScript] instance.\n * @param radius The radius of the blur.\n * @param sampling The sampling multiplier used to scale the image. Values > 1\n *  will downscale the image. Values between 0 and 1 will upscale the image.\n */\nclass BlurTransformation @JvmOverloads constructor(\n    private val context: Context,\n    private val radius: Float = DEFAULT_RADIUS,\n    private val sampling: Float = DEFAULT_SAMPLING,\n) : Transformation {\n\n    init {\n        require(radius in 0.0..25.0) { \"radius must be in [0, 25].\" }\n        require(sampling > 0) { \"sampling must be > 0.\" }\n    }\n\n    @Suppress(\"NullableToStringCall\")\n    override val cacheKey = \"${BlurTransformation::class.java.name}-$radius-$sampling\"\n\n    override suspend fun transform(input: Bitmap, size: Size): Bitmap {\n        val paint = Paint(Paint.ANTI_ALIAS_FLAG or Paint.FILTER_BITMAP_FLAG)\n\n        val scaledWidth = (input.width / sampling).toInt()\n        val scaledHeight = (input.height / sampling).toInt()\n        val output =\n            createBitmap(scaledWidth, scaledHeight, input.config ?: Bitmap.Config.ARGB_8888)\n        output.applyCanvas {\n            scale(1 / sampling, 1 / sampling)\n            drawBitmap(input, 0f, 0f, paint)\n        }\n\n        var script: RenderScript? = null\n        var tmpInt: Allocation? = null\n        var tmpOut: Allocation? = null\n        var blur: ScriptIntrinsicBlur? = null\n        try {\n            script = RenderScript.create(context)\n            tmpInt = Allocation.createFromBitmap(\n                script,\n                output,\n                Allocation.MipmapControl.MIPMAP_NONE,\n                Allocation.USAGE_SCRIPT\n            )\n            tmpOut = Allocation.createTyped(script, tmpInt.type)\n            blur = ScriptIntrinsicBlur.create(script, Element.U8_4(script))\n            blur.setRadius(radius)\n            blur.setInput(tmpInt)\n            blur.forEach(tmpOut)\n            tmpOut.copyTo(output)\n        } finally {\n            script?.destroy()\n            tmpInt?.destroy()\n            tmpOut?.destroy()\n            blur?.destroy()\n        }\n\n        return output\n    }\n\n    private companion object {\n        private const val DEFAULT_RADIUS = 10f\n        private const val DEFAULT_SAMPLING = 1f\n    }\n}"
  },
  {
    "path": "app/src/main/java/com/yenaly/han1meviewer/ui/view/CenterLinearLayoutManager.kt",
    "content": "package com.yenaly.han1meviewer.ui.view\n\nimport android.content.Context\nimport android.util.AttributeSet\nimport androidx.recyclerview.widget.LinearLayoutManager\nimport androidx.recyclerview.widget.LinearSmoothScroller\nimport androidx.recyclerview.widget.RecyclerView\nimport kotlin.math.abs\n\nopen class CenterLinearLayoutManager : LinearLayoutManager {\n    constructor(context: Context)\n            : super(context)\n\n    constructor(context: Context, orientation: Int, reverseLayout: Boolean)\n            : super(context, orientation, reverseLayout)\n\n    constructor(context: Context, attrs: AttributeSet, defStyleAttr: Int, defStyleRes: Int)\n            : super(context, attrs, defStyleAttr, defStyleRes)\n\n    companion object {\n        // Shrink the cards around the center up to 15%\n        private const val SHRINK_AMOUNT = 0.15f\n\n        // The cards will be at 15% when they are 80% of the way between the\n        // center and the edge.\n        private const val SHRINK_DISTANCE = 0.8f\n    }\n\n    override fun smoothScrollToPosition(\n        recyclerView: RecyclerView,\n        state: RecyclerView.State,\n        position: Int,\n    ) {\n        val centerSmoothScroller = CenterSmoothScroller(recyclerView.context)\n        centerSmoothScroller.targetPosition = position\n        startSmoothScroll(centerSmoothScroller)\n    }\n\n    override fun scrollHorizontallyBy(\n        dx: Int,\n        recycler: RecyclerView.Recycler?,\n        state: RecyclerView.State?,\n    ): Int {\n        return if (orientation == HORIZONTAL) {\n            val scrolled = super.scrollHorizontallyBy(dx, recycler, state)\n            val midpoint = width / 2f\n            val d0 = 0f\n            val d1: Float = SHRINK_DISTANCE * midpoint\n            val s0 = 1f\n            val s1: Float = 1f - SHRINK_AMOUNT\n            for (i in 0..<childCount) {\n                val child = getChildAt(i) ?: continue\n                val childMidpoint = (getDecoratedRight(child) + getDecoratedLeft(child)) / 2f\n                val d = d1.coerceAtMost(abs(midpoint - childMidpoint))\n                val scale = s0 + (s1 - s0) * (d - d0) / (d1 - d0)\n                child.scaleX = scale\n                child.scaleY = scale\n            }\n            scrolled\n        } else {\n            0\n        }\n    }\n\n    override fun scrollVerticallyBy(\n        dy: Int,\n        recycler: RecyclerView.Recycler?,\n        state: RecyclerView.State?,\n    ): Int {\n        return if (orientation == VERTICAL) {\n            val scrolled = super.scrollVerticallyBy(dy, recycler, state)\n            val midpoint = height / 2f\n            val d0 = 0f\n            val d1 = SHRINK_DISTANCE * midpoint\n            val s0 = 1f\n            val s1 = 1f - SHRINK_AMOUNT\n            for (i in 0..<childCount) {\n                val child = getChildAt(i) ?: continue\n                val childMidpoint = (getDecoratedBottom(child) + getDecoratedTop(child)) / 2f\n                val d = d1.coerceAtMost(abs(midpoint - childMidpoint))\n                val scale = s0 + (s1 - s0) * (d - d0) / (d1 - d0)\n                child.scaleX = scale\n                child.scaleY = scale\n            }\n            scrolled\n        } else {\n            0\n        }\n    }\n\n    private class CenterSmoothScroller(context: Context) : LinearSmoothScroller(context) {\n        override fun calculateDtToFit(\n            viewStart: Int,\n            viewEnd: Int,\n            boxStart: Int,\n            boxEnd: Int,\n            snapPreference: Int,\n        ): Int = (boxStart + (boxEnd - boxStart) / 2) - (viewStart + (viewEnd - viewStart) / 2)\n    }\n}"
  },
  {
    "path": "app/src/main/java/com/yenaly/han1meviewer/ui/view/CollapsibleTags.kt",
    "content": "package com.yenaly.han1meviewer.ui.view\n\nimport android.animation.ValueAnimator\nimport android.content.Context\nimport android.os.Parcelable\nimport android.util.AttributeSet\nimport android.view.LayoutInflater\nimport android.view.View\nimport android.widget.FrameLayout\nimport androidx.core.view.isVisible\nimport androidx.core.view.updateLayoutParams\nimport androidx.interpolator.view.animation.FastOutSlowInInterpolator\nimport androidx.lifecycle.Lifecycle\nimport androidx.lifecycle.coroutineScope\nimport com.google.android.material.button.MaterialButton\nimport com.google.android.material.card.MaterialCardView\nimport com.google.android.material.chip.Chip\nimport com.google.android.material.chip.ChipGroup\nimport com.yenaly.han1meviewer.ADVANCED_SEARCH_MAP\nimport com.yenaly.han1meviewer.R\nimport com.yenaly.han1meviewer.ui.activity.SearchActivity\nimport com.yenaly.han1meviewer.util.addUpdateListener\nimport com.yenaly.yenaly_libs.utils.activity\nimport com.yenaly.yenaly_libs.utils.copyToClipboard\nimport com.yenaly.yenaly_libs.utils.showShortToast\nimport com.yenaly.yenaly_libs.utils.startActivity\nimport kotlinx.coroutines.launch\nimport kotlinx.parcelize.Parcelize\n\n\n/**\n * 可折叠 TAG 栏\n *\n * @project Han1meViewer\n * @author Yenaly Liew\n * @time 2023/08/06 006 21:46\n */\nclass CollapsibleTags @JvmOverloads constructor(\n    context: Context, attrs: AttributeSet? = null,\n) : FrameLayout(context, attrs) {\n\n\n    companion object {\n        val animInterpolator = FastOutSlowInInterpolator()\n        const val ANIM_DURATION = 300L\n    }\n\n    /**\n     * 原本用来异步加载 Tag，但有这个必要吗？\n     *\n     * 每次创建还得多传一个 lifecycle，以后可能把这个砍了\n     */\n    var lifecycle: Lifecycle? = null\n\n    /**\n     * 设置当前是否折叠，同时作为监听器，\n     * 修改这里的值会改变折叠状态\n     */\n    var isCollapsed = false\n        set(value) {\n            field = value\n            post { handleWhenCollapsed(value) }\n        }\n\n    var isCollapsedEnabled = true\n        set(value) {\n            field = value\n            toggleButton.isVisible = value\n        }\n\n    /**\n     * 從這裏設置tags\n     *\n     * post很重要，因為要等到View被加入到Window才能取得父View的寬度，\n     * 在RecyclerView中不這麽設置會出現問題。\n     */\n    var tags: List<String>? = null\n        set(value) {\n            field = value\n            post {\n                setTagsInternal(value ?: emptyList())\n            }\n        }\n\n    private var tagViewList: MutableList<Chip>? = null\n        set(value) {\n            field = value\n            chipGroup.removeAllViews()\n            value?.forEach(chipGroup::addView)\n            chipGroupMeasureHeight = chipGroup.calcHeight()\n            collapseValueAnimator = buildChipGroupAnimator(chipGroupMeasureHeight, 0)\n            expandValueAnimator = buildChipGroupAnimator(0, chipGroupMeasureHeight)\n        }\n\n    private var chipGroupMeasureHeight = 0\n    private var collapseValueAnimator: ValueAnimator? = null\n    private var expandValueAnimator: ValueAnimator? = null\n\n    private val tagCardView: MaterialCardView\n    private val toggleButton: MaterialButton\n    private val chipGroup: ChipGroup\n\n    init {\n        inflate(context, R.layout.layout_collapsible_tag, this)\n        tagCardView = findViewById(R.id.tag_card_view)\n        toggleButton = findViewById(R.id.toggle_button)\n        chipGroup = findViewById(R.id.tag_group)\n\n        // default\n        toggleButton.isVisible = isCollapsedEnabled\n        chipGroup.visibility = VISIBLE\n        toggleButton.setOnClickListener {\n            isCollapsed = !isCollapsed\n        }\n\n        post {\n            toggleButton.animate().rotation(if (isCollapsed) 0F else 180F)\n                .setDuration(ANIM_DURATION)\n                .setInterpolator(animInterpolator).start()\n        }\n    }\n\n    private fun setTagsInternal(tags: List<String>) {\n        lifecycle?.coroutineScope?.launch {\n            tagViewList = tags.map { tag ->\n                (LayoutInflater.from(context).inflate(\n                    R.layout.item_video_tag_chip, this@CollapsibleTags, false\n                ) as Chip).apply {\n                    text = tag\n                    setOnClickListener {\n                        context?.activity?.startActivity<SearchActivity>(ADVANCED_SEARCH_MAP to tag)\n                    }\n                    setOnLongClickListener {\n                        tag.copyToClipboard()\n                        showShortToast(context.getString(R.string.s_copy_to_clipboard, tag))\n                        return@setOnLongClickListener true\n                    }\n                }\n            }.toMutableList()\n        }\n    }\n\n    private fun handleWhenCollapsed(isCollapsed: Boolean) {\n        toggleButton.animate().rotation(if (isCollapsed) 0F else 180F).setDuration(ANIM_DURATION)\n            .setInterpolator(animInterpolator).start()\n\n        if (isCollapsed) {\n            chipGroup.animate().setDuration(ANIM_DURATION).setInterpolator(animInterpolator)\n                .alpha(0F).withStartAction {\n                    collapseValueAnimator?.start()\n                }.withEndAction {\n                    chipGroup.visibility = INVISIBLE\n                }.start()\n        } else {\n            chipGroup.animate().setDuration(ANIM_DURATION).setInterpolator(animInterpolator)\n                .alpha(1F).withStartAction {\n                    chipGroup.visibility = VISIBLE\n                    expandValueAnimator?.start()\n                }.start()\n        }\n    }\n\n    private fun View.calcHeight(): Int {\n        val matchParentMeasureSpec =\n            MeasureSpec.makeMeasureSpec((parent as View).width, MeasureSpec.EXACTLY)\n        val wrapContentMeasureSpec = MeasureSpec.makeMeasureSpec(0, MeasureSpec.UNSPECIFIED)\n        measure(matchParentMeasureSpec, wrapContentMeasureSpec)\n        return measuredHeight\n    }\n\n    private fun buildChipGroupAnimator(start: Int, end: Int): ValueAnimator {\n        return ValueAnimator.ofInt(start, end).apply {\n            duration = ANIM_DURATION\n            interpolator = animInterpolator\n            addUpdateListener(lifecycle) {\n                val value = it.animatedValue as Int\n                chipGroup.updateLayoutParams {\n                    height = value\n                }\n            }\n        }\n    }\n\n    override fun onSaveInstanceState(): Parcelable {\n        return SavedState(\n            super.onSaveInstanceState(),\n            isCollapsed, isCollapsedEnabled, chipGroupMeasureHeight, tags\n        )\n    }\n\n    override fun onRestoreInstanceState(state: Parcelable?) {\n        if (state !is SavedState) {\n            super.onRestoreInstanceState(state)\n            return\n        }\n\n        super.onRestoreInstanceState(state.superState)\n        this.isCollapsed = state.isCollapsed\n        this.isCollapsedEnabled = state.isCollapsedEnabled\n        this.chipGroupMeasureHeight = state.chipGroupMeasureHeight\n        this.tags = state.tags\n    }\n\n    @Parcelize\n    data class SavedState(\n        val ss: Parcelable?,\n        val isCollapsed: Boolean,\n        val isCollapsedEnabled: Boolean,\n        val chipGroupMeasureHeight: Int,\n        val tags: List<String>?\n    ) : BaseSavedState(ss)\n}"
  },
  {
    "path": "app/src/main/java/com/yenaly/han1meviewer/ui/view/HOptionChip.kt",
    "content": "package com.yenaly.han1meviewer.ui.view\n\nimport android.animation.ValueAnimator\nimport android.content.Context\nimport android.graphics.Color\nimport android.graphics.drawable.GradientDrawable\nimport android.os.Parcelable\nimport android.util.AttributeSet\nimport android.widget.Checkable\nimport androidx.appcompat.widget.AppCompatTextView\nimport androidx.core.content.res.use\nimport androidx.core.graphics.ColorUtils\nimport androidx.core.view.updatePadding\nimport androidx.interpolator.view.animation.FastOutSlowInInterpolator\nimport com.yenaly.han1meviewer.R\nimport com.yenaly.yenaly_libs.utils.dp\nimport kotlinx.parcelize.Parcelize\n\nclass HOptionChip @JvmOverloads constructor(\n    context: Context, attrs: AttributeSet? = null, defStyleRes: Int = 0\n) : AppCompatTextView(context, attrs, defStyleRes), Checkable {\n\n    private val cornerRadius = 12.dp.toFloat()\n    private val unselectedColor = context.getColor(R.color.adv_search_unselected_color)\n    private val selectedColor = context.getColor(R.color.adv_search_selected_color)\n\n    private var mIsChecked: Boolean = false\n\n    init {\n        context.obtainStyledAttributes(intArrayOf(android.R.attr.selectableItemBackground)).use {\n            val bgRes = it.getResourceId(0, 0)\n            setBackgroundResource(bgRes)\n        }\n        // Set default properties\n        updatePadding(top = 12.dp, bottom = 12.dp)\n        setTextColor(Color.WHITE)\n        // corner radius drawable\n        background = GradientDrawable().apply {\n            cornerRadius = this@HOptionChip.cornerRadius\n            setColor(unselectedColor)\n        }\n    }\n\n    private fun animateChipByColorTransition(enable: Boolean) {\n        val startColor = if (enable) unselectedColor else selectedColor\n        val endColor = if (enable) selectedColor else unselectedColor\n\n        val animator = ValueAnimator.ofArgb(startColor, endColor)\n        animator.addUpdateListener { animation ->\n            background = GradientDrawable().apply {\n                cornerRadius = this@HOptionChip.cornerRadius\n                setColor(animation.animatedValue as Int)\n            }\n        }\n        animator.interpolator = FastOutSlowInInterpolator()\n        animator.duration = 300\n        animator.start()\n    }\n\n    var isAvailable: Boolean = true\n        set(available) {\n            field = available\n            isEnabled = available\n            val gd = background as? GradientDrawable\n            if (available) {\n                gd?.setColor(if (isChecked) selectedColor else unselectedColor)\n            } else {\n                gd?.setColor(ColorUtils.setAlphaComponent(unselectedColor, 0x80))\n            }\n        }\n\n    override fun setChecked(checked: Boolean) {\n        if (mIsChecked == checked) return\n        mIsChecked = checked\n        animateChipByColorTransition(checked)\n    }\n\n    override fun isChecked(): Boolean = mIsChecked\n\n    override fun toggle() {\n        isChecked = !isChecked\n    }\n\n    override fun onSaveInstanceState(): Parcelable {\n        return SavedState(super.onSaveInstanceState(), isChecked)\n    }\n\n    override fun onRestoreInstanceState(state: Parcelable?) {\n        if (state !is SavedState) {\n            super.onRestoreInstanceState(state)\n            return\n        }\n\n        super.onRestoreInstanceState(state.superState)\n        this.isChecked = state.isChecked\n    }\n\n    @Parcelize\n    data class SavedState(\n        val ss: Parcelable?,\n        val isChecked: Boolean\n    ) : BaseSavedState(ss)\n}"
  },
  {
    "path": "app/src/main/java/com/yenaly/han1meviewer/ui/view/HanimeSearchBar.kt",
    "content": "package com.yenaly.han1meviewer.ui.view\n\nimport android.animation.LayoutTransition\nimport android.content.Context\nimport android.os.Parcelable\nimport android.util.AttributeSet\nimport android.util.Log\nimport android.view.View\nimport android.view.ViewGroup\nimport android.view.inputmethod.EditorInfo\nimport android.widget.FrameLayout\nimport androidx.core.widget.addTextChangedListener\nimport androidx.interpolator.view.animation.FastOutSlowInInterpolator\nimport androidx.recyclerview.widget.LinearLayoutManager\nimport androidx.recyclerview.widget.RecyclerView\nimport com.chad.library.adapter4.BaseQuickAdapter\nimport com.chad.library.adapter4.viewholder.QuickViewHolder\nimport com.google.android.material.button.MaterialButton\nimport com.google.android.material.textfield.TextInputEditText\nimport com.yenaly.han1meviewer.R\nimport com.yenaly.han1meviewer.logic.entity.SearchHistoryEntity\nimport com.yenaly.yenaly_libs.utils.activity\nimport com.yenaly.yenaly_libs.utils.view.hideIme\nimport kotlinx.coroutines.channels.awaitClose\nimport kotlinx.coroutines.flow.callbackFlow\nimport kotlinx.parcelize.Parcelize\n\n/**\n * 搜索界面的搜索栏\n *\n * @project Han1meViewer\n * @author Yenaly Liew\n * @time 2023/08/11 011 15:20\n */\nclass HanimeSearchBar @JvmOverloads constructor(\n    context: Context, attrs: AttributeSet? = null,\n) : FrameLayout(context, attrs) {\n\n    companion object {\n        val animInterpolator = FastOutSlowInInterpolator()\n        const val ANIM_DURATION = 300L\n    }\n\n    private val window = checkNotNull(context.activity?.window)\n\n    private val root = inflate(context, R.layout.layout_hanime_search_bar, this) as ViewGroup\n    private val back: MaterialButton = findViewById(R.id.btn_back)\n    private val search: MaterialButton = findViewById(R.id.btn_search)\n    private val tag: MaterialButton = findViewById(R.id.btn_tag)\n    private val rvHistory: RecyclerView = findViewById(R.id.rv_history)\n    private val etSearch: TextInputEditText = findViewById(R.id.et_search)\n\n    /**\n     * 历史记录是否折叠\n     */\n    private var isCollapsed = true\n\n    init {\n        // init\n        root.layoutTransition = LayoutTransition().apply {\n            enableTransitionType(LayoutTransition.CHANGING)\n            setDuration(LayoutTransition.CHANGING, ANIM_DURATION)\n            setInterpolator(LayoutTransition.CHANGING, animInterpolator)\n        }\n        rvHistory.layoutManager = LinearLayoutManager(context)\n\n        // #issue-176: 禁用默认动画，涉及到许多崩溃，到现在官方也没解决问题，不得不牺牲体验\n        // 根本问题：RecyclerView 的 itemAnimator 与 LayoutTransition 有冲突\n        // 与其相关的网站链接：\n        // https://github.com/google/flexbox-layout/issues/240\n        // https://github.com/airbnb/epoxy/issues/689\n        // https://stackoverflow.com/questions/30078834\n        rvHistory.itemAnimator = null\n\n        etSearch.setOnEditorActionListener { _, actionId, _ ->\n            if (actionId == EditorInfo.IME_ACTION_SEARCH) {\n                onSearchClickListener?.let { listener ->\n                    listener(etSearch, etSearch.text?.toString().orEmpty())\n                    true\n                } == true\n            }\n            false\n        }\n    }\n\n    var canTextChange: Boolean = true\n        set(value) {\n            field = value\n            etSearch.isEnabled = value\n        }\n\n    var historyAdapter: BaseQuickAdapter<SearchHistoryEntity, out QuickViewHolder>? = null\n        set(value) {\n            field = value\n            rvHistory.adapter = value\n        }\n\n\n    var onBackClickListener: ((View) -> Unit)? = null\n        set(value) {\n            field = value\n            back.setOnClickListener {\n                if (isCollapsed) {\n                    value?.invoke(it)\n                } else {\n                    hideHistory()\n                }\n            }\n        }\n\n    var onSearchClickListener: ((View, String) -> Unit)? = null\n        set(value) {\n            field = value\n            search.setOnClickListener {\n                etSearch.hideIme(window)\n                value?.invoke(it, etSearch.text?.toString().orEmpty())\n            }\n        }\n\n    var onTagClickListener: ((View) -> Unit)? = null\n        set(value) {\n            field = value\n            tag.setOnClickListener(value)\n        }\n\n    var searchText: String?\n        get() = etSearch.text?.toString()\n        set(value) {\n            etSearch.setText(value)\n            etSearch.setSelection(etSearch.length())\n        }\n\n    /**\n     * 文字修改或者获得焦点的Flow\n     */\n    fun textChangeFlow() = callbackFlow {\n        val watcher = etSearch.addTextChangedListener { text ->\n            trySend(text?.toString())\n            Log.d(\"HanimeSearchBar\", \"watcher: $text\")\n        }\n\n        etSearch.setOnFocusChangeListener { _, hasFocus ->\n            if (hasFocus) {\n                showHistory()\n                trySend(searchText)\n                Log.d(\"HanimeSearchBar\", \"focus: $searchText\")\n            } else {\n                hideHistory()\n            }\n        }\n\n        awaitClose {\n            etSearch.removeTextChangedListener(watcher)\n            etSearch.onFocusChangeListener = null\n        }\n    }\n\n    fun showHistory() {\n//        val slide = Slide(Gravity.BOTTOM).apply {\n//            duration = animDuration\n//            interpolator = animInterpolator\n//            addTarget(rvHistory)\n//        }\n//        TransitionManager.beginDelayedTransition(searchBar, slide)\n\n        rvHistory.visibility = VISIBLE\n        back.animate()\n            .setInterpolator(animInterpolator)\n            .setDuration(ANIM_DURATION)\n            .rotation(45F)\n            .start()\n        isCollapsed = false\n    }\n\n    fun hideHistory(): Boolean {\n        if (isCollapsed) return false\n        etSearch.hideIme(window)\n        Log.d(\"HanimeSearchBar\", \"History Height: ${rvHistory.height}\")\n//        val slide = Slide(Gravity.TOP).apply {\n//            duration = animDuration\n//            interpolator = animInterpolator\n//            addTarget(rvHistory)\n//        }\n//        TransitionManager.beginDelayedTransition(searchBar, slide)\n\n        rvHistory.visibility = GONE\n        back.animate()\n            .setInterpolator(animInterpolator)\n            .setDuration(ANIM_DURATION)\n            .rotation(0F)\n            .start()\n        isCollapsed = true\n        return true\n    }\n\n    // 使用 onBackPressedDispatcher.addCallback 替换\n    // 这个方法在 API 34 以上（貌似）无法返回 key event\n    //\n    // override fun dispatchKeyEvent(event: KeyEvent): Boolean {\n    //     if (event.keyCode == KeyEvent.KEYCODE_BACK && !isCollapsed) {\n    //         hideHistory()\n    //         return true\n    //     }\n    //     return super.dispatchKeyEvent(event)\n    // }\n\n    override fun onSaveInstanceState(): Parcelable {\n        return SavedState(super.onSaveInstanceState(), isCollapsed)\n    }\n\n    override fun onRestoreInstanceState(state: Parcelable?) {\n        if (state !is SavedState) {\n            super.onRestoreInstanceState(state)\n            return\n        }\n\n        super.onRestoreInstanceState(state.superState)\n        this.isCollapsed = state.isCollapsed\n        if (!isCollapsed) {\n            showHistory()\n        }\n    }\n\n    @Parcelize\n    data class SavedState(\n        val ss: Parcelable?,\n        val isCollapsed: Boolean\n    ) : BaseSavedState(ss)\n}"
  },
  {
    "path": "app/src/main/java/com/yenaly/han1meviewer/ui/view/HorizontalNestedScrollView.kt",
    "content": "package com.yenaly.han1meviewer.ui.view\n\nimport android.content.Context\nimport android.util.AttributeSet\nimport android.view.MotionEvent\nimport android.widget.HorizontalScrollView\nimport kotlin.math.abs\n\n/**\n * 暂时是专门为影片界面中的横向功能滚动条而设计的\n *\n * @project Han1meViewer\n * @author Yenaly Liew\n * @time 2023/08/21 021 16:05\n */\nclass HorizontalNestedScrollView @JvmOverloads constructor(\n    context: Context, attrs: AttributeSet? = null,\n) : HorizontalScrollView(context, attrs) {\n\n    private var disallowIntercept = false\n    private var startX = 0\n    private var startY = 0\n\n    override fun dispatchTouchEvent(ev: MotionEvent): Boolean {\n        when (ev.action) {\n            MotionEvent.ACTION_DOWN -> {\n                startX = ev.x.toInt()\n                startY = ev.y.toInt()\n                parent.requestDisallowInterceptTouchEvent(true)\n            }\n\n            MotionEvent.ACTION_MOVE -> {\n                val endX = ev.x.toInt()\n                val endY = ev.y.toInt()\n                val disX = abs(endX - startX)\n                val disY = abs(endY - startY)\n                if (disX > disY) {\n                    if (disallowIntercept) {\n                        parent.requestDisallowInterceptTouchEvent(disallowIntercept)\n                    } else {\n                        // 防止划到下一页\n                        parent.requestDisallowInterceptTouchEvent(true)\n                    }\n                } else {\n                    parent.requestDisallowInterceptTouchEvent(false)\n                }\n            }\n\n            MotionEvent.ACTION_UP, MotionEvent.ACTION_CANCEL -> {\n                parent.requestDisallowInterceptTouchEvent(false)\n            }\n        }\n        return super.dispatchTouchEvent(ev)\n    }\n\n    override fun requestDisallowInterceptTouchEvent(disallowIntercept: Boolean) {\n        this.disallowIntercept = disallowIntercept\n        super.requestDisallowInterceptTouchEvent(disallowIntercept)\n    }\n}"
  },
  {
    "path": "app/src/main/java/com/yenaly/han1meviewer/ui/view/LinearSmoothToStartScroller.kt",
    "content": "package com.yenaly.han1meviewer.ui.view\n\nimport android.content.Context\nimport androidx.recyclerview.widget.LinearSmoothScroller\n\n/**\n * https://stackoverflow.com/questions/31235183\n *\n * @project Han1meViewer\n * @author Yenaly Liew\n * @time 2024/04/04 004 17:49\n */\nclass LinearSmoothToStartScroller(context: Context?) : LinearSmoothScroller(context) {\n    override fun getVerticalSnapPreference(): Int = SNAP_TO_START\n\n    override fun getHorizontalSnapPreference(): Int = SNAP_TO_START\n}"
  },
  {
    "path": "app/src/main/java/com/yenaly/han1meviewer/ui/view/PlaylistHeader.kt",
    "content": "package com.yenaly.han1meviewer.ui.view\n\nimport android.annotation.SuppressLint\nimport android.content.Context\nimport android.util.AttributeSet\nimport android.view.LayoutInflater\nimport android.widget.Button\nimport android.widget.EditText\nimport android.widget.FrameLayout\nimport android.widget.TextView\nimport com.yenaly.han1meviewer.R\nimport com.yenaly.han1meviewer.util.showAlertDialog\n\n/**\n * 用于播放清單的標題和介紹\n *\n * @project Han1meViewer\n * @author Yenaly Liew\n * @time 2023/08/30 030 00:28\n */\nclass PlaylistHeader @JvmOverloads constructor(\n    context: Context, attrs: AttributeSet? = null,\n) : FrameLayout(context, attrs) {\n\n    private val tvTitle: TextView\n    private val btnDelete: Button\n    private val tvDesc: TextView\n    private val btnEdit: Button\n\n    init {\n        inflate(context, R.layout.layout_playlist_header_v2, this)\n        tvTitle = findViewById(R.id.tv_title)\n        btnDelete = findViewById(R.id.btn_delete)\n        tvDesc = findViewById(R.id.tv_desc)\n        btnEdit = findViewById(R.id.btn_edit)\n        init()\n    }\n\n    var title: String? = null\n        set(value) {\n            field = value\n            tvTitle.text = value\n        }\n\n    var description: String? = null\n        set(value) {\n            field = value\n            tvDesc.text =\n                if (!value.isNullOrEmpty()) value else context.getString(R.string.no_description)\n        }\n\n    var onChangedListener: ((title: String, desc: String) -> Unit)? = null\n\n    var onDeleteActionListener: (() -> Unit)? = null\n\n    @SuppressLint(\"InflateParams\")\n    private fun init() {\n        btnDelete.setOnClickListener {\n            context.showAlertDialog {\n                setTitle(R.string.delete_the_playlist)\n                setMessage(R.string.sure_to_delete)\n                setPositiveButton(R.string.confirm) { _, _ ->\n                    onDeleteActionListener?.invoke()\n                }\n                setNegativeButton(R.string.cancel, null)\n            }\n        }\n        btnEdit.setOnClickListener {\n            context.showAlertDialog {\n                setTitle(R.string.modify_title_or_desc)\n                val etView =\n                    LayoutInflater.from(context)\n                        .inflate(R.layout.dialog_playlist_modify_edit_text, null)\n                val etTitle = etView.findViewById<EditText>(R.id.et_title)\n                val etDesc = etView.findViewById<EditText>(R.id.et_desc)\n                etTitle.setText(title)\n                etDesc.setText(description)\n                setView(etView)\n                setPositiveButton(R.string.confirm) { _, _ ->\n                    onChangedListener?.invoke(\n                        etTitle.text.toString(),\n                        etDesc.text.toString()\n                    )\n                }\n                setNegativeButton(R.string.cancel, null)\n            }\n        }\n    }\n}"
  },
  {
    "path": "app/src/main/java/com/yenaly/han1meviewer/ui/view/funcbar/Hanidapter.kt",
    "content": "package com.yenaly.han1meviewer.ui.view.funcbar\n\nimport android.content.Context\nimport android.view.View\nimport android.view.ViewGroup\nimport androidx.appcompat.widget.TooltipCompat\nimport androidx.recyclerview.widget.DiffUtil\nimport com.chad.library.adapter4.BaseDifferAdapter\nimport com.chad.library.adapter4.viewholder.QuickViewHolder\nimport com.google.android.material.button.MaterialButton\nimport com.yenaly.han1meviewer.R\nimport com.yenaly.yenaly_libs.utils.logFieldsChange\n\n/**\n * @project Han1meViewer\n * @author Yenaly Liew\n * @since 2025/3/11 22:05\n */\nclass Hanidapter : BaseDifferAdapter<Hanidokitem, QuickViewHolder>(Hanidiff) {\n\n    companion object {\n        const val ICON = 1 shl 0\n        const val TEXT = 1 shl 1\n        const val BACK = 1 shl 2\n    }\n\n    val hanidontroller = Hanidontroller()\n\n    private object Hanidiff : DiffUtil.ItemCallback<Hanidokitem>() {\n\n        private const val TAG = \"Hanidiff\"\n\n        override fun areItemsTheSame(oldItem: Hanidokitem, newItem: Hanidokitem): Boolean {\n            return oldItem contentEquals newItem\n        }\n\n        override fun areContentsTheSame(oldItem: Hanidokitem, newItem: Hanidokitem): Boolean {\n            return oldItem == newItem\n        }\n\n        override fun getChangePayload(oldItem: Hanidokitem, newItem: Hanidokitem): Int {\n            logFieldsChange(TAG, oldItem, newItem)\n            var mask = 0\n            if (oldItem.icon != newItem.icon) {\n                mask = mask or ICON\n            }\n            if (oldItem.text != newItem.text) {\n                mask = mask or TEXT\n            }\n            if (oldItem.isBack != newItem.isBack) {\n                mask = mask or BACK\n            }\n            return mask\n        }\n    }\n\n    override fun onBindViewHolder(holder: QuickViewHolder, position: Int, item: Hanidokitem?) {\n        item ?: return\n        val button = holder.itemView as MaterialButton\n        if (item.isBack) {\n            button.setIconResource(R.drawable.ic_baseline_arrow_back_24)\n            TooltipCompat.setTooltipText(button, context.getString(R.string.back))\n        } else {\n            button.setIconResource(item.icon)\n            TooltipCompat.setTooltipText(button, context.getString(item.text))\n        }\n        if (item.text != 0) {\n            button.iconGravity = MaterialButton.ICON_GRAVITY_TEXT_TOP\n        } else {\n            button.iconGravity = MaterialButton.ICON_GRAVITY_TEXT_START\n        }\n        val action = item.viewAction\n        button.setOnClickListener { view ->\n            if (item.isBack) {\n                if (hanidontroller.onBackPressed()) {\n                    items = hanidontroller.currentHanidokitems\n                }\n            } else if (hanidontroller.onItemClicked(item)) {\n                items = hanidontroller.currentHanidokitems\n            }\n            if (action != null && !item.isBack) {\n                action.onClick(view)\n            }\n        }\n\n    }\n\n    override fun onBindViewHolder(\n        holder: QuickViewHolder,\n        position: Int,\n        item: Hanidokitem?,\n        payloads: List<Any>\n    ) {\n        item ?: return\n        if (payloads.isEmpty() || payloads.first() == 0) {\n            return super.onBindViewHolder(holder, position, item, payloads)\n        }\n        val button = holder.itemView as MaterialButton\n        val mask = payloads.first() as Int\n        if (mask and ICON != 0) {\n            button.setIconResource(item.icon)\n        }\n        if (mask and TEXT != 0) {\n            TooltipCompat.setTooltipText(button, context.getString(item.text))\n            if (item.text != 0) {\n                button.iconGravity = MaterialButton.ICON_GRAVITY_TEXT_TOP\n            } else {\n                button.iconGravity = MaterialButton.ICON_GRAVITY_TEXT_START\n            }\n        }\n        if (mask and BACK != 0) {\n            if (item.isBack) {\n                button.setIconResource(R.drawable.ic_baseline_arrow_back_24)\n                TooltipCompat.setTooltipText(button, context.getString(R.string.back))\n            } else {\n                button.setIconResource(item.icon)\n                TooltipCompat.setTooltipText(button, context.getString(item.text))\n            }\n            val action = item.viewAction\n            button.setOnClickListener { view ->\n                if (item.isBack) {\n                    if (hanidontroller.onBackPressed()) {\n                        items = hanidontroller.currentHanidokitems\n                    }\n                } else if (hanidontroller.onItemClicked(item)) {\n                    items = hanidontroller.currentHanidokitems\n                }\n                if (action != null && !item.isBack) {\n                    action.onClick(view)\n                }\n            }\n        }\n    }\n\n    override fun onCreateViewHolder(\n        context: Context,\n        parent: ViewGroup,\n        viewType: Int\n    ): QuickViewHolder {\n        // MaterialButton 构成\n        val view = View.inflate(context, R.layout.item_hanidock, null)\n        return QuickViewHolder(view)\n    }\n}"
  },
  {
    "path": "app/src/main/java/com/yenaly/han1meviewer/ui/view/funcbar/Hanidock.kt",
    "content": "package com.yenaly.han1meviewer.ui.view.funcbar\n\nimport android.content.Context\nimport android.util.AttributeSet\nimport android.widget.FrameLayout\nimport androidx.recyclerview.widget.LinearLayoutManager\nimport androidx.recyclerview.widget.RecyclerView\nimport com.yenaly.han1meviewer.R\n\n/**\n * @project Han1meViewer\n * @author Yenaly Liew\n * @since 2025/3/11 22:02\n */\nclass Hanidock @JvmOverloads constructor(\n    context: Context, attrs: AttributeSet? = null, defStyleAttr: Int = 0,\n) : FrameLayout(context, attrs, defStyleAttr) {\n\n    private val recyclerView: RecyclerView\n    private val hanidapter = Hanidapter()\n\n    init {\n        inflate(context, R.layout.layout_hanidock, this)\n        recyclerView = findViewById(R.id.rv_func)\n        recyclerView.layoutManager = LinearLayoutManager(context)\n        recyclerView.adapter = hanidapter\n    }\n\n    var hanidokitems: List<Hanidokitem>\n        get() = hanidapter.hanidontroller.currentHanidokitems\n        set(value) {\n            hanidapter.hanidontroller.initialize(value)\n            hanidapter.items = value\n        }\n}"
  },
  {
    "path": "app/src/main/java/com/yenaly/han1meviewer/ui/view/funcbar/Hanidokitem.kt",
    "content": "package com.yenaly.han1meviewer.ui.view.funcbar\n\nimport android.view.View.OnClickListener\nimport androidx.annotation.DrawableRes\nimport androidx.annotation.StringRes\n\n/**\n * @project Han1meViewer\n * @author Yenaly Liew\n * @since 2025/3/11 22:10\n */\ndata class Hanidokitem(\n    @DrawableRes var icon: Int = 0,\n    @StringRes var text: Int = 0,\n    var viewAction: OnClickListener? = null,\n    var subitems: List<Hanidokitem> = emptyList(),\n    private val _isBack: Boolean = false,\n) {\n\n    val isBack: Boolean\n        get() = this._isBack\n\n    infix fun contentEquals(other: Hanidokitem): Boolean = icon == other.icon && text == other.text\n\n    companion object {\n        @JvmStatic\n        inline fun create(action: Hanidokitem.() -> Unit): Hanidokitem = Hanidokitem().apply(action)\n    }\n}"
  },
  {
    "path": "app/src/main/java/com/yenaly/han1meviewer/ui/view/funcbar/Hanidontroller.kt",
    "content": "package com.yenaly.han1meviewer.ui.view.funcbar\n\n/**\n * @project Han1meViewer\n * @author Yenaly Liew\n * @since 2025/3/11 22:06\n */\nclass Hanidontroller {\n\n    /**\n     * 当前层级\n     *\n     */\n    var level = 0\n        private set\n\n    private val navigationStack = ArrayDeque<Hanidokitem>()\n    private val pastStack = ArrayDeque<Hanidokitem?>()\n\n    /**\n     * 初始化\n     */\n    fun initialize(hanidokitems: List<Hanidokitem>) {\n        navigationStack.clear()\n        pastStack.clear()\n        navigationStack.addAll(hanidokitems)\n    }\n\n    /**\n     * item 点击事件\n     *\n     * @param item item\n     * @return true 如果存在子项，且 UI 需要更新。true if there are sub-items and the UI needs to be updated.\n     */\n    fun onItemClicked(item: Hanidokitem): Boolean {\n        if (item.subitems.isNotEmpty()) {\n            // Add a null to the past stack to indicate that we are going to a new item\n            pastStack.addLast(null)\n            while (navigationStack.isNotEmpty()) {\n                pastStack.addLast(navigationStack.removeLast())\n            }\n            level++\n            navigationStack.addLast(item.copy(_isBack = true))\n            navigationStack.addAll(item.subitems)\n            return true\n        }\n        return false\n    }\n\n    /**\n     * 返回事件\n     *\n     * @return true 如果存在上一个层级，且 UI 需要更新。true if there is a previous level and the UI needs to be updated.\n     */\n    fun onBackPressed(): Boolean {\n        if (level > 0) {\n            navigationStack.clear()\n\n            while (pastStack.isNotEmpty()) {\n                // Find the last null marker in the past stack\n                val item = pastStack.removeLast() ?: break\n                navigationStack.addLast(item)\n            }\n            level--\n\n            return true\n        }\n        return false\n    }\n\n    /**\n     * 当前层级的 Hanidokitem 列表\n     */\n    val currentHanidokitems: List<Hanidokitem> get() = navigationStack.toList()\n}"
  },
  {
    "path": "app/src/main/java/com/yenaly/han1meviewer/ui/view/pref/HPrivacyPreference.kt",
    "content": "package com.yenaly.han1meviewer.ui.view.pref\n\nimport android.content.Context\nimport android.util.AttributeSet\nimport android.view.ViewGroup\nimport android.widget.TextView\nimport androidx.appcompat.app.AlertDialog\nimport androidx.core.text.method.LinkMovementMethodCompat\nimport androidx.core.text.parseAsHtml\nimport com.yenaly.han1meviewer.R\nimport com.yenaly.han1meviewer.util.createAlertDialog\nimport com.yenaly.han1meviewer.util.showWithBlurEffect\nimport com.yenaly.yenaly_libs.base.preference.LongClickableSwitchPreference\n\n/**\n * 隐私设置视图\n *\n * @since 2024/10/16\n */\nclass HPrivacyPreference(\n    context: Context,\n    attrs: AttributeSet? = null\n) : LongClickableSwitchPreference(context, attrs) {\n\n    val privacyDialog = createAnalyticsDialog(context).apply {\n        setOnShowListener {\n            // support link click\n            val ad = it as AlertDialog\n            val anchorView = ad.getButton(AlertDialog.BUTTON_POSITIVE)\n            val contentView = anchorView.rootView as ViewGroup\n            contentView.findViewById<TextView>(android.R.id.message).apply {\n                movementMethod = LinkMovementMethodCompat.getInstance()\n            }\n\n            // set click listener\n            ad.getButton(AlertDialog.BUTTON_POSITIVE).setOnClickListener {\n                if (callChangeListener(true)) {\n                    isChecked = true\n                }\n                ad.dismiss()\n            }\n            ad.getButton(AlertDialog.BUTTON_NEUTRAL).setOnClickListener {\n                if (callChangeListener(false)) {\n                    isChecked = false\n                }\n                ad.dismiss()\n            }\n        }\n    }\n\n    override fun onClick() {\n        if (isChecked) {\n            privacyDialog.showWithBlurEffect()\n        } else {\n            super.onClick()\n        }\n    }\n\n    private fun createAnalyticsDialog(context: Context): AlertDialog {\n        return context.createAlertDialog {\n            setTitle(R.string.about_analytics)\n            setMessage(context.getString(R.string.about_analytics_summary).parseAsHtml())\n            setPositiveButton(R.string.ok, null)\n            setNeutralButton(R.string.deny, null)\n        }\n    }\n}\n"
  },
  {
    "path": "app/src/main/java/com/yenaly/han1meviewer/ui/view/pref/MaterialDialogPreference.kt",
    "content": "package com.yenaly.han1meviewer.ui.view.pref\n\nimport android.content.Context\nimport android.util.AttributeSet\nimport androidx.preference.ListPreference\nimport com.google.android.material.dialog.MaterialAlertDialogBuilder\nimport com.yenaly.han1meviewer.util.getDialogDefaultDrawable\nimport com.yenaly.han1meviewer.util.showWithBlurEffect\n\n/**\n * @project Han1meViewer\n * @author Yenaly Liew\n * @time 2024/03/06 006 22:37\n */\nclass MaterialDialogPreference : ListPreference {\n\n    constructor(\n        context: Context,\n    ) : this(context, null)\n\n    constructor(\n        context: Context,\n        attrs: AttributeSet?,\n    ) : super(context, attrs) {\n        dialog = MaterialAlertDialogBuilder(context)\n    }\n\n    private val dialog: MaterialAlertDialogBuilder\n\n    override fun onClick() {\n        dialog.setTitle(title)\n        dialog.setBackground(context.getDialogDefaultDrawable())\n        dialog.setSingleChoiceItems(entries, findIndexOfValue(value)) { di, which ->\n            val str = entryValues[which].toString()\n            if (callChangeListener(str)) {\n                this.value = str\n                di.dismiss()\n            }\n        }\n        dialog.create().showWithBlurEffect()\n    }\n}"
  },
  {
    "path": "app/src/main/java/com/yenaly/han1meviewer/ui/view/video/HJzvdStd.kt",
    "content": "package com.yenaly.han1meviewer.ui.view.video\n\nimport android.annotation.SuppressLint\nimport android.content.Context\nimport android.content.pm.ActivityInfo\nimport android.graphics.Typeface\nimport android.media.AudioManager\nimport android.provider.Settings\nimport android.provider.Settings.SettingNotFoundException\nimport android.util.AttributeSet\nimport android.util.Log\nimport android.view.GestureDetector\nimport android.view.Gravity\nimport android.view.HapticFeedbackConstants\nimport android.view.LayoutInflater\nimport android.view.MotionEvent\nimport android.view.View\nimport android.view.View.OnLongClickListener\nimport android.view.ViewGroup\nimport android.widget.FrameLayout\nimport android.widget.ImageView\nimport android.widget.PopupWindow\nimport android.widget.ProgressBar\nimport android.widget.TextView\nimport androidx.annotation.IntRange\nimport androidx.core.content.getSystemService\nimport androidx.core.view.isGone\nimport androidx.core.view.isInvisible\nimport androidx.core.view.isVisible\nimport androidx.core.view.updatePadding\nimport androidx.recyclerview.widget.LinearLayoutManager\nimport androidx.recyclerview.widget.RecyclerView\nimport cn.jzvd.JZDataSource\nimport cn.jzvd.JZMediaInterface\nimport cn.jzvd.JZTextureView\nimport cn.jzvd.JZUtils\nimport cn.jzvd.JzvdStd\nimport com.itxca.spannablex.spannable\nimport com.yenaly.han1meviewer.Preferences\nimport com.yenaly.han1meviewer.R\nimport com.yenaly.han1meviewer.logic.entity.HKeyframeEntity\nimport com.yenaly.han1meviewer.ui.adapter.HKeyframeRvAdapter\nimport com.yenaly.han1meviewer.ui.adapter.SuperResolutionAdapter\nimport com.yenaly.han1meviewer.ui.adapter.VideoSpeedAdapter\nimport com.yenaly.han1meviewer.util.Platform\nimport com.yenaly.han1meviewer.util.setStateViewLayout\nimport com.yenaly.han1meviewer.util.showAlertDialog\nimport com.yenaly.yenaly_libs.utils.OrientationManager\nimport com.yenaly.yenaly_libs.utils.activity\nimport com.yenaly.yenaly_libs.utils.appScreenWidth\nimport com.yenaly.yenaly_libs.utils.navBarHeight\nimport com.yenaly.yenaly_libs.utils.statusBarHeight\nimport com.yenaly.yenaly_libs.utils.unsafeLazy\nimport com.yenaly.yenaly_libs.utils.view.removeItself\nimport java.util.Timer\nimport kotlin.math.absoluteValue\n\n/**\n * @project Hanime1\n * @author Yenaly Liew\n * @time 2022/06/18 018 15:54\n */\nclass HJzvdStd @JvmOverloads constructor(\n    context: Context,\n    attrs: AttributeSet? = null,\n) : JzvdStd(context, attrs), OnLongClickListener {\n\n    companion object {\n        // 相當於重寫了\n        /**\n         * 滑动操作的阈值\n         */\n        const val THRESHOLD = 10\n\n        // 相當於重寫了\n        /**\n         * 默認滑動調整進度條的靈敏度 越大播放进度条滑动越慢\n         */\n        const val DEF_PROGRESS_SLIDE_SENSITIVITY = 5\n\n        const val DEF_COUNTDOWN_SEC = 10\n\n        /**\n         * 默認速度\n         */\n        const val DEF_SPEED = 1.0F\n\n        /**\n         * 默認速度的索引\n         */\n        const val DEF_SPEED_INDEX = 2\n\n        /**\n         * 默认的超分辨率的索引\n         */\n        const val DEF_SUPER_RESOLUTION_INDEX = 0\n\n        /**\n         * 默認長按速度是原先速度的幾倍\n         */\n        const val DEF_LONG_PRESS_SPEED_TIMES = 2.5F\n\n        /**\n         * 速度列表\n         */\n        val speedArray = floatArrayOf(\n            0.5F, 0.75F,\n            1.0F, 1.25F, 1.5F, 1.75F,\n            2.0F, 2.25F, 2.5F, 2.75F,\n            3.0F,\n        )\n\n        /**\n         * 速度列表的字符串\n         */\n        val speedStringArray = Array(speedArray.size) { \"${speedArray[it]}x\" }\n\n        /**\n         * 超分辨率列表\n         */\n        val superResolutionArray = arrayOf(\n            \"关闭\", \"速度\", \"质量\"\n        )\n    }\n\n    init {\n        gestureDetector = GestureDetector(context,\n            object : GestureDetector.SimpleOnGestureListener() {\n                override fun onDoubleTap(e: MotionEvent): Boolean {\n                    if (state == STATE_PLAYING || state == STATE_PAUSE) {\n                        Log.d(TAG, \"doubleClick [\" + this.hashCode() + \"] \")\n                        startButton.performClick()\n                    }\n                    return super.onDoubleTap(e)\n                }\n\n                override fun onSingleTapConfirmed(e: MotionEvent): Boolean {\n                    if (!mChangeBrightness && !mChangeVolume) {\n                        onClickUiToggle()\n                    }\n                    return super.onSingleTapConfirmed(e)\n                }\n            })\n    }\n\n    /**\n     * 用戶定義的是否顯示底部進度條\n     */\n    private val showBottomProgress = Preferences.showBottomProgress\n\n    /**\n     * 用戶定義的默認速度\n     */\n    private val userDefSpeed = Preferences.playerSpeed\n\n    /**\n     * 用戶定義的默認速度的索引\n     */\n    private val userDefSpeedIndex = speedArray.indexOfFirst { it == userDefSpeed }\n\n    /**\n     * 用戶定義的滑動調整進度條的靈敏度\n     */\n    private val userDefSlideSensitivity = Preferences.slideSensitivity.toRealSensitivity()\n\n    /**\n     * 用戶定義的默認長按速度是原先速度的幾倍\n     */\n    private val userDefLongPressSpeedTimes = Preferences.longPressSpeedTime\n\n    /**\n     * 用戶定義的倒數提醒毫秒數\n     */\n    private val userDefWhenCountdownRemind = Preferences.whenCountdownRemind\n\n    /**\n     * 用戶定義的是否在倒數時顯示評論\n     */\n    private val userDefShowCommentWhenCountdown = Preferences.showCommentWhenCountdown\n\n    /**\n     * 用戶定義的是否啟用關鍵H幀\n     */\n    private val isHKeyframeEnabled = Preferences.hKeyframesEnable\n\n    /**\n     * 用户选择的播放器内核\n     */\n    private val switchPlayerKernel = Preferences.switchPlayerKernel\n\n    /**\n     * 當前速度的索引，如果设置速度的话，修改这个，别动 [videoSpeed]\n     */\n    private var currentSpeedIndex = userDefSpeedIndex\n        @SuppressLint(\"SetTextI18n\")\n        set(value) {\n            field = value\n            if (value == DEF_SPEED_INDEX) {\n                tvSpeed.text = context.getString(R.string.speed)\n            } else {\n                tvSpeed.text = speedStringArray[value]\n            }\n            videoSpeed = speedArray[value]\n            // #issue-14: 有些机器到这里可能会报空指针异常，所以加了个判断，但是不知道为什么会报空指针异常\n            if (jzDataSource.objects == null) {\n                jzDataSource.objects = arrayOf(userDefSpeedIndex)\n            }\n            jzDataSource.objects[0] = value\n        }\n\n    private var superResolutionIndex = 0\n        set(value) {\n            field = value\n            if (value == DEF_SUPER_RESOLUTION_INDEX) {\n                superResolution.text = context.getString(R.string.super_resolution)\n            } else {\n                superResolution.text = superResolutionArray[value]\n            }\n            if (mediaInterface is MpvMediaKernel) {\n                val kernel = mediaInterface as MpvMediaKernel\n                kernel.setSuperResolution(value)\n            }\n        }\n\n    /**\n     * 是否进入了垂直全屏模式\n     */\n    private var isVerticalFullscreen = false\n\n    private lateinit var tvSpeed: TextView\n    private lateinit var superResolution: TextView\n    private lateinit var tvKeyframe: TextView\n    private lateinit var tvTimer: TextView\n    private lateinit var btnGoHome: ImageView\n    private lateinit var layoutTop: View\n    private lateinit var layoutBottom: View\n    private lateinit var verticalFullscreen: View\n\n    var hKeyframe: HKeyframeEntity? = null\n        set(value) {\n            field = value\n            hKeyframeAdapter.submitList(value?.keyframes)\n            hKeyframeAdapter.isLocal = value?.let { it.author == null } ?: true\n        }\n\n    var videoCode: String? = null\n\n    private val hKeyframeAdapter: HKeyframeRvAdapter by unsafeLazy { initHKeyframeAdapter() }\n\n    /**\n     * 初始化關鍵H幀的 Adapter，最好不用 lazy\n     *\n     * 但我還是最終用了 lazy，要不然首次 submitList 收不到\n     */\n    private fun initHKeyframeAdapter() = run {\n        val videoCode = checkNotNull(this.videoCode) {\n            \"If you want to use HKeyframeAdapter, you must set videoCode first.\"\n        }\n        HKeyframeRvAdapter(videoCode).apply {\n            setOnItemClickListener { _, _, position ->\n                val keyframe = getItem(position) ?: return@setOnItemClickListener\n                mediaInterface?.seekTo(keyframe.position)\n                startProgressTimer()\n            }\n        }\n    }\n\n    /**\n     * 關鍵H幀的點擊事件\n     *\n     * 作用：打開 Dialog，顯示關鍵H幀的列表\n     */\n    var onKeyframeClickListener: ((View) -> Unit)? = null\n\n    /**\n     * 回到主頁的點擊事件\n     *\n     * 作用：關閉所有的 VideoActivity\n     */\n    var onGoHomeClickListener: ((View) -> Unit)? = null\n\n    /**\n     * 關鍵H幀的長按事件\n     *\n     * 作用：將當前時刻加入關鍵H幀\n     */\n    var onKeyframeLongClickListener: ((View) -> Unit)? = null\n\n    private var videoSpeed: Float = userDefSpeed\n        set(value) {\n            field = value\n            mediaInterface?.let { mi ->\n                val isPlaying = mi.isPlaying\n                mi.setSpeed(value)\n                if (!isPlaying) {\n                    mi.pause()\n                }\n            }\n        }\n\n    /**\n     * 是否觸發了長按快進\n     */\n    @Volatile\n    private var isSpeedGestureDetected = false\n\n    /**\n     * 長按快進檢測\n     */\n    // #issue-20: 长按倍速功能添加\n    private val speedGestureDetector =\n        GestureDetector(context, object : GestureDetector.SimpleOnGestureListener() {\n            override fun onLongPress(e: MotionEvent) {\n                when (e.action) {\n                    MotionEvent.ACTION_DOWN -> {\n                        val mi: JZMediaInterface? = mediaInterface\n                        if (mi != null && mi.isPlaying) {\n                            setSpeedInternal(videoSpeed * userDefLongPressSpeedTimes)\n                            textureViewContainer.performHapticFeedback(HapticFeedbackConstants.LONG_PRESS)\n                            isSpeedGestureDetected = true\n                        }\n                    }\n                }\n            }\n        })\n\n    override fun getLayoutId() = R.layout.layout_jzvd_with_speed\n\n    override fun init(context: Context?) {\n        super.init(context)\n        SAVE_PROGRESS = false\n        tvSpeed = findViewById(R.id.tv_speed)\n        superResolution = findViewById(R.id.super_resolution)\n        tvKeyframe = findViewById(R.id.tv_keyframe)\n        tvTimer = findViewById(R.id.tv_timer)\n        btnGoHome = findViewById(R.id.go_home)\n        layoutTop = findViewById(R.id.layout_top)\n        layoutBottom = findViewById(R.id.layout_bottom)\n        verticalFullscreen = findViewById(R.id.vertical_fullscreen)\n        textureViewContainer.isHapticFeedbackEnabled = true\n        tvSpeed.setOnClickListener(this)\n        tvKeyframe.setOnClickListener(this)\n        tvKeyframe.setOnLongClickListener(this)\n        btnGoHome.setOnClickListener(this)\n        superResolution.setOnClickListener(this)\n        verticalFullscreen.setOnClickListener(this)\n    }\n\n    override fun setUp(jzDataSource: JZDataSource?, screen: Int) {\n        Log.d(TAG, \"setUp: 2\")\n        super.setUp(jzDataSource, screen, ExoMediaKernel::class.java)\n    }\n\n    fun setUp(jzDataSource: JZDataSource?, screen: Int, kernel: HMediaKernel.Type) {\n        setUp(jzDataSource, screen, kernel.clazz)\n    }\n\n    override fun setUp(jzDataSource: JZDataSource?, screen: Int, clazz: Class<*>) {\n        Log.d(TAG, \"setUp: 3\")\n        super.setUp(jzDataSource, screen, clazz)\n        Log.d(\"CustomJzvdStd-Settings\", buildString {\n            append(\"showBottomProgress: \")\n            appendLine(showBottomProgress)\n            append(\"userDefSpeed: \")\n            appendLine(userDefSpeed)\n            append(\"userDefSpeedIndex: \")\n            appendLine(userDefSpeedIndex)\n            append(\"userDefSlideSensitivity: \")\n            appendLine(userDefSlideSensitivity)\n        })\n        titleTextView.isInvisible = true\n        if (bottomProgressBar != null && !showBottomProgress) {\n            bottomProgressBar.removeItself()\n            bottomProgressBar = ProgressBar(context)\n        }\n    }\n\n    override fun onTouch(v: View, event: MotionEvent): Boolean {\n        when (v.id) {\n            R.id.surface_container -> {\n                speedGestureDetector.onTouchEvent(event)\n                when (event.action) {\n                    MotionEvent.ACTION_UP -> {\n                        if (isSpeedGestureDetected) {\n                            setSpeedInternal(videoSpeed)\n                            isSpeedGestureDetected = false\n                        }\n                    }\n                }\n            }\n        }\n        return super.onTouch(v, event)\n    }\n\n    fun autoFullscreen(orientation: OrientationManager.ScreenOrientation) {\n        autoFullscreen(if (orientation === OrientationManager.ScreenOrientation.LANDSCAPE) 1.0f else -1.0f)\n    }\n\n    override fun onClickUiToggle() {\n        if (!bottomContainer.isVisible) {\n            setSystemTimeAndBattery()\n            clarity.text = jzDataSource.currentKey.toString()\n        }\n        when (state) {\n            STATE_PREPARING -> {\n                changeUiToPreparing()\n                if (!bottomContainer.isVisible) {\n                    setSystemTimeAndBattery()\n                }\n            }\n\n            STATE_PLAYING -> {\n                if (bottomContainer.isVisible) {\n                    changeUiToPlayingClear()\n                } else {\n                    changeUiToPlayingShow()\n                }\n            }\n\n            STATE_PAUSE -> {\n                if (bottomContainer.isVisible) {\n                    changeUiToPauseClear()\n                } else {\n                    changeUiToPauseShow()\n                }\n            }\n\n            STATE_PREPARING_PLAYING -> {\n                if (bottomContainer.isVisible) {\n                    changeUiToPreparingPlayingClear()\n                } else {\n                    changeUiToPreparingPlayingShow()\n                }\n            }\n        }\n    }\n\n    override fun onStatePreparingPlaying() {\n        super.onStatePreparingPlaying()\n        if (jzDataSource.objects == null) {\n            jzDataSource.objects = arrayOf(userDefSpeedIndex)\n            currentSpeedIndex = userDefSpeedIndex\n        } else {\n            currentSpeedIndex = jzDataSource.objects.first() as Int\n        }\n    }\n\n    // #issue-232: 快进滑动一加载就会出现操作栏，很影响观看体验\n    override fun changeUIToPreparingPlaying() {\n        when (screen) {\n            SCREEN_FULLSCREEN -> {\n                setAllControlsVisiblity(\n                    INVISIBLE, INVISIBLE, INVISIBLE,\n                    VISIBLE, INVISIBLE, INVISIBLE, INVISIBLE\n                )\n                updateStartImage()\n            }\n        }\n    }\n\n    override fun setScreenNormal() {\n        super.setScreenNormal()\n        updateVideoPlayerSize(false)\n\n        backButton.isVisible = true\n        tvSpeed.isVisible = false\n        superResolution.isVisible = false\n        tvKeyframe.isVisible = false\n        titleTextView.isInvisible = true\n        tvTimer.isInvisible = true\n        btnGoHome.isVisible = true\n\n        layoutTop.updatePadding(left = 0, right = 0)\n        layoutBottom.updatePadding(left = 0, right = 0)\n        tvTimer.updatePadding(left = 0, right = 0)\n        bottomProgressBar.updatePadding(left = 0, right = 0)\n    }\n\n    override fun setScreenFullscreen() {\n        super.setScreenFullscreen()\n        updateVideoPlayerSize(true)\n\n        tvSpeed.isVisible = true\n        // 非 MpvPlayer 内核不支持超分辨率\n        superResolution.isVisible = switchPlayerKernel == HMediaKernel.Type.MpvPlayer.name\n        if (isHKeyframeEnabled) tvKeyframe.isVisible = true\n        titleTextView.isVisible = true\n        // btnGoHome.isVisible = false\n\n        val statusBarHeight = statusBarHeight\n        val navBarHeight = navBarHeight\n        layoutTop.updatePadding(left = statusBarHeight, right = navBarHeight)\n        layoutBottom.updatePadding(left = statusBarHeight, right = navBarHeight)\n        tvTimer.updatePadding(left = statusBarHeight)\n        bottomProgressBar.updatePadding(left = statusBarHeight, right = navBarHeight)\n    }\n\n    /**\n     * 主动改变播放器大小\n     */\n    private fun updateVideoPlayerSize(fullscreen: Boolean) {\n        if (mediaInterface is MpvMediaKernel) {\n            val kernel = mediaInterface as MpvMediaKernel\n            post {\n                if (fullscreen) {\n                    kernel.updateSurFaceSize(width, height)\n                } else {\n                    kernel.updateSurFaceSize(width, height)\n                }\n            }\n        }\n    }\n    override fun clickBack() {\n        Log.i(TAG, \"clickBack\")\n        when {\n            CONTAINER_LIST.isNotEmpty() && CURRENT_JZVD != null -> { //判断条件，因为当前所有goBack都是回到普通窗口\n                CURRENT_JZVD.gotoNormalScreen()\n            }\n\n            CONTAINER_LIST.isEmpty() && CURRENT_JZVD != null && CURRENT_JZVD.screen != SCREEN_NORMAL -> { //退出直接进入的全屏\n                CURRENT_JZVD.clearFloatScreen()\n            }\n\n            else -> { //剩餘情況直接退出\n                context.activity?.finish()\n            }\n        }\n    }\n\n    override fun onClick(v: View) {\n        super.onClick(v)\n        when (v.id) {\n            R.id.tv_speed -> clickSpeed()\n            R.id.super_resolution -> clickSuperResolution()\n            R.id.tv_keyframe -> onKeyframeClickListener?.invoke(v)\n            R.id.go_home -> onGoHomeClickListener?.invoke(v)\n            R.id.vertical_fullscreen -> clickVerticalFullscreen()\n        }\n    }\n\n    override fun onLongClick(v: View): Boolean {\n        return when (v.id) {\n            R.id.tv_keyframe -> {\n                onKeyframeLongClickListener?.invoke(v)\n                return true\n            }\n\n            else -> false\n        }\n    }\n\n    override fun onCompletion() {\n        if (screen == SCREEN_FULLSCREEN) {\n            onStateAutoComplete()\n        } else {\n            super.onCompletion()\n        }\n        posterImageView.isGone = true\n    }\n\n    override fun touchActionMove(x: Float, y: Float) {\n        Log.i(TAG, \"onTouch surfaceContainer actionMove [\" + this.hashCode() + \"] \")\n        val deltaX = x - mDownX\n        var deltaY = y - mDownY\n        val absDeltaX = deltaX.absoluteValue\n        val absDeltaY = deltaY.absoluteValue\n        // 此處進行了修改，未全屏也能調節進度\n        if (screen != SCREEN_TINY && !isSpeedGestureDetected) {\n            //拖动的是NavigationBar和状态栏\n            if (mDownX > JZUtils.getScreenWidth(context)\n                || mDownY < JZUtils.getStatusBarHeight(context)\n            ) {\n                return\n            }\n            if (!mChangePosition && !mChangeVolume && !mChangeBrightness) {\n                if (absDeltaX > THRESHOLD || absDeltaY > THRESHOLD) {\n                    cancelProgressTimer()\n                    if (absDeltaX >= THRESHOLD) {\n                        // 全屏模式下的CURRENT_STATE_ERROR状态下,不响应进度拖动事件.\n                        // 否则会因为media player的状态非法导致App Crash\n                        if (state != STATE_ERROR) {\n                            mChangePosition = true\n                            mGestureDownPosition = currentPositionWhenPlaying\n                        }\n                    } else {\n                        //如果y轴滑动距离超过设置的处理范围，那么进行滑动事件处理\n                        if (mDownX < appScreenWidth * 0.5f) { //左侧改变亮度\n                            mChangeBrightness = true\n                            val lp = JZUtils.getWindow(context).attributes\n                            if (lp.screenBrightness < 0) {\n                                try {\n                                    mGestureDownBrightness = Settings.System.getInt(\n                                        context.contentResolver,\n                                        Settings.System.SCREEN_BRIGHTNESS\n                                    ).toFloat()\n                                    Log.i(\n                                        TAG,\n                                        \"current system brightness: $mGestureDownBrightness\"\n                                    )\n                                } catch (e: SettingNotFoundException) {\n                                    e.printStackTrace()\n                                }\n                            } else {\n                                mGestureDownBrightness = lp.screenBrightness * 255\n                                Log.i(\n                                    TAG,\n                                    \"current activity brightness: $mGestureDownBrightness\"\n                                )\n                            }\n                        } else { //右侧改变声音\n                            mChangeVolume = true\n                            if (mAudioManager == null) {\n                                mAudioManager = context.getSystemService()\n                            }\n                            mGestureDownVolume =\n                                mAudioManager.getStreamVolume(AudioManager.STREAM_MUSIC)\n                        }\n                    }\n                }\n            }\n        }\n\n        if (mChangePosition) {\n            val totalTimeDuration = duration\n            mSeekTimePosition =\n                (mGestureDownPosition + deltaX * totalTimeDuration / (mScreenWidth * userDefSlideSensitivity)).toLong()\n            if (mSeekTimePosition > totalTimeDuration) mSeekTimePosition = totalTimeDuration\n            val seekTime = JZUtils.stringForTime(mSeekTimePosition)\n            val totalTime = JZUtils.stringForTime(totalTimeDuration)\n            showProgressDialog(deltaX, seekTime, mSeekTimePosition, totalTime, totalTimeDuration)\n        }\n\n        if (mChangeVolume) {\n            deltaY = -deltaY\n            val max = mAudioManager.getStreamMaxVolume(AudioManager.STREAM_MUSIC)\n            val deltaV = (max * deltaY * 3 / mScreenHeight).toInt()\n            mAudioManager.setStreamVolume(AudioManager.STREAM_MUSIC, mGestureDownVolume + deltaV, 0)\n            //dialog中显示百分比\n            val volumePercent =\n                (mGestureDownVolume * 100 / max + deltaY * 3 * 100 / mScreenHeight).toInt()\n            showVolumeDialog(-deltaY, volumePercent)\n        }\n\n        if (mChangeBrightness) {\n            deltaY = -deltaY\n            val deltaV = (255 * deltaY * 3 / mScreenHeight).toInt()\n            val params = JZUtils.getWindow(context).attributes\n            if ((mGestureDownBrightness + deltaV) / 255 >= 1) { //这和声音有区别，必须自己过滤一下负值\n                params.screenBrightness = 1f\n            } else if ((mGestureDownBrightness + deltaV) / 255 <= 0) {\n                params.screenBrightness = 0.01f\n            } else {\n                params.screenBrightness = (mGestureDownBrightness + deltaV) / 255\n            }\n            JZUtils.getWindow(context).attributes = params\n            //dialog中显示百分比\n            val brightnessPercent =\n                (mGestureDownBrightness * 100 / 255 + deltaY * 3 * 100 / mScreenHeight).toInt()\n            showBrightnessDialog(brightnessPercent)\n//            mDownY = y;\n        }\n    }\n\n    override fun gotoNormalScreen() {\n        gobakFullscreenTime = System.currentTimeMillis()\n        val vg = JZUtils.scanForActivity(context).window.decorView as ViewGroup\n        vg.removeView(this)\n        // #issue-crashlytics-1b3cebd1278de6fc52230eb5517be879:\n        // 你永远要给不更新的 JZVD 擦屁股才能保持稳定\n        CONTAINER_LIST.peekLast()?.apply {\n            removeViewAt(blockIndex)\n            addView(this@HJzvdStd, blockIndex, blockLayoutParams)\n        }\n        CONTAINER_LIST.poll()\n        setScreenNormal()\n        JZUtils.showStatusBar(jzvdContext)\n        JZUtils.setRequestedOrientation(jzvdContext, NORMAL_ORIENTATION)\n        JZUtils.showSystemUI(jzvdContext)\n\n        setVerticalFullscreenVisibility()\n        if (isVerticalFullscreen) {\n            isVerticalFullscreen = false\n            setActionVisibility()\n        }\n    }\n\n    override fun gotoFullscreen() {\n        super.gotoFullscreen()\n        verticalFullscreen.isVisible = false\n    }\n\n    override fun onStatePreparingChangeUrl() {\n        Log.i(TAG, \"onStatePreparingChangeUrl \" + \" [\" + this.hashCode() + \"] \")\n        state = STATE_PREPARING_CHANGE_URL\n\n        // 原方法直接使用下面的方法，會導致全屏切換清晰度返回正常界面時重置影片。\n        // 所以重寫，只抄過調用的方法的一部分。\n        // releaseAllVideos()\n        CURRENT_JZVD?.let {\n            it.reset()\n            CURRENT_JZVD = null\n        }\n\n        startVideo()\n    }\n\n    override fun showWifiDialog() {\n        jzvdContext.showAlertDialog {\n            setTitle(\"Warning!\")\n            setMessage(cn.jzvd.R.string.tips_not_wifi)\n            setPositiveButton(cn.jzvd.R.string.tips_not_wifi_confirm) { _, _ ->\n                WIFI_TIP_DIALOG_SHOWED = true\n                if (state == STATE_PAUSE) startButton.performClick() else startVideo()\n            }\n            setNegativeButton(cn.jzvd.R.string.tips_not_wifi_cancel) { _, _ ->\n                releaseAllVideos()\n                clearFloatScreen()\n            }\n        }\n    }\n\n    // 原來是 300 period 我改成了 100 爲了計時準確\n    override fun startProgressTimer() {\n        Log.i(TAG, \"startProgressTimer: \" + \" [\" + this.hashCode() + \"] \")\n        cancelProgressTimer()\n        UPDATE_PROGRESS_TIMER = Timer()\n        mProgressTimerTask = ProgressTimerTask()\n        UPDATE_PROGRESS_TIMER.schedule(mProgressTimerTask, 0, 100)\n    }\n\n    override fun onProgress(progress: Int, position: Long, duration: Long) {\n        super.onProgress(progress, position, duration)\n        if (screen == SCREEN_FULLSCREEN) hKeyframe?.let {\n            var match = false\n            for ((index, kf) in it.keyframes.withIndex()) {\n                val interval = kf.position - position\n                if (interval in 0L..<userDefWhenCountdownRemind) {\n                    val timeLong = interval / 1_000L\n                    val spannable = spannable {\n                        if (userDefShowCommentWhenCountdown) {\n                            \"#${index + 1}\".span {\n                                relativeSize(proportion = 0.7F)\n                            }\n                            if (!kf.prompt.isNullOrBlank()) {\n                                \" ${kf.prompt}\".span {\n                                    relativeSize(proportion = 0.7F)\n                                }\n                            }\n                            newline()\n                        }\n                        val time = if (timeLong >= 1) {\n                            (timeLong + 1).toString()\n                        } else {\n                            val timeFloat = interval / 1_000F\n                            \"%.1f\".format(timeFloat)\n                        }\n                        time.span {\n                            style(Typeface.BOLD)\n                        }\n                    }\n                    tvTimer.text = spannable\n                    match = true\n                    break\n                }\n            }\n            tvTimer.isInvisible = !match\n        } ?: run { tvTimer.isInvisible = true }\n    }\n\n    override fun onStatePlaying() {\n        super.onStatePlaying()\n        setVerticalFullscreenVisibility()\n    }\n\n    private fun changeUiToPreparingPlayingClear() {\n        when (screen) {\n            SCREEN_NORMAL, SCREEN_FULLSCREEN -> {\n                setAllControlsVisiblity(\n                    INVISIBLE, INVISIBLE, INVISIBLE,\n                    VISIBLE, INVISIBLE, INVISIBLE, INVISIBLE\n                )\n            }\n        }\n    }\n\n    private fun changeUiToPreparingPlayingShow() {\n        when (screen) {\n            SCREEN_NORMAL, SCREEN_FULLSCREEN -> {\n                setAllControlsVisiblity(\n                    VISIBLE, VISIBLE, INVISIBLE,\n                    VISIBLE, INVISIBLE, VISIBLE, INVISIBLE\n                )\n            }\n        }\n    }\n\n    // #issue-14: 之前用 XPopup 三键模式下会有 bug，无法呼出，所以换成这个\n    @SuppressLint(\"InflateParams\")\n    fun clickSpeed() {\n        onCLickUiToggleToClear()\n        val inflater = LayoutInflater.from(context).inflate(R.layout.jz_layout_speed, null)\n        val rv = inflater.findViewById<RecyclerView>(R.id.rv_video_speed)\n        val popup = PopupWindow(\n            inflater, JZUtils.dip2px(jzvdContext, 240f),\n            LayoutParams.MATCH_PARENT, true\n        ).apply {\n            contentView = inflater\n            animationStyle = cn.jzvd.R.style.pop_animation\n        }\n        rv.layoutManager = LinearLayoutManager(context)\n        rv.adapter = VideoSpeedAdapter(currentSpeedIndex).apply {\n            setOnItemClickListener { _, _, position ->\n                currentSpeedIndex = position\n                popup.dismiss()\n            }\n        }\n        popup.showAtLocation(textureViewContainer, Gravity.END, 0, 0)\n    }\n\n    @SuppressLint(\"InflateParams\")\n    fun clickSuperResolution() {\n        onCLickUiToggleToClear()\n        val inflater = LayoutInflater.from(context).inflate(R.layout.jz_layout_speed, null)\n        val rv = inflater.findViewById<RecyclerView>(R.id.rv_video_speed)\n        val popup = PopupWindow(\n            inflater, JZUtils.dip2px(jzvdContext, 240f),\n            LayoutParams.MATCH_PARENT, true\n        ).apply {\n            contentView = inflater\n            animationStyle = cn.jzvd.R.style.pop_animation\n        }\n        rv.layoutManager = LinearLayoutManager(context)\n        rv.adapter = SuperResolutionAdapter(superResolutionIndex).apply {\n            setOnItemClickListener { _, _, position ->\n                superResolutionIndex = position\n                popup.dismiss()\n            }\n        }\n        popup.showAtLocation(textureViewContainer, Gravity.END, 0, 0)\n    }\n\n    fun setActionVisibility() {\n        clarity.isVisible = false\n        superResolution.isVisible = false\n        tvKeyframe.isVisible = false\n        verticalFullscreen.isVisible = !isVerticalFullscreen\n    }\n\n    fun setVerticalFullscreenVisibility() {\n        if (mediaInterface is IMedia) {\n            val media = mediaInterface as IMedia\n            Log.d(TAG, \"onPrepared: ${media.width}x${media.height} ${media.ratio}\")\n            // 视频比例小于1时，显示垂直全屏\n            verticalFullscreen.isVisible = media.ratio < 1\n        }\n    }\n\n    fun clickVerticalFullscreen() {\n        onCLickUiToggleToClear()\n        if (isVerticalFullscreen) {\n            gotoNormalScreen()\n            return\n        }\n        FULLSCREEN_ORIENTATION = ActivityInfo.SCREEN_ORIENTATION_PORTRAIT\n        gotoFullscreen()\n        FULLSCREEN_ORIENTATION = ActivityInfo.SCREEN_ORIENTATION_SENSOR_LANDSCAPE\n    }\n\n    @SuppressLint(\"InflateParams\")\n    fun clickHKeyframe(v: View) {\n        onCLickUiToggleToClear()\n        val inflater = LayoutInflater.from(context).inflate(R.layout.jz_layout_speed, null)\n        val rv = inflater.findViewById<RecyclerView>(R.id.rv_video_speed)\n        val popup = PopupWindow(\n            inflater, JZUtils.dip2px(jzvdContext, 240f),\n            LayoutParams.MATCH_PARENT, true\n        ).apply {\n            contentView = inflater\n            animationStyle = cn.jzvd.R.style.pop_animation\n        }\n        rv.layoutManager = LinearLayoutManager(v.context)\n        val adapter = hKeyframeAdapter\n        rv.adapter = adapter\n        adapter.setStateViewLayout(\n            inflate(v.context, R.layout.layout_empty_view, null),\n            this@HJzvdStd.context.getString(R.string.here_is_empty) + \"\\n\"\n                    + this@HJzvdStd.context.getString(R.string.long_press_to_add_h_keyframe)\n        )\n        popup.showAtLocation(textureViewContainer, Gravity.END, 0, 0)\n    }\n\n    /**\n     * 这个 setSpeed 的 bug 太多了，不同机型效果不一定相同，不得不套个 try-catch。 (previous)\n     *\n     * PS: 套 try-catch 没用，因为在 post 里面，所以还是会报错，只能在调用的地方 try-catch 了。\n     *\n     * #issue-28 就是这个问题，如果我在 HJZMediaSystem 中 setSpeed 方法里加的判断不起作用，\n     * 那么那个机型就先别用这个功能了。\n     */\n    private fun setSpeedInternal(speed: Float) {\n        mediaInterface?.setSpeed(speed)\n    }\n\n    /**\n     * 將靈敏度轉換為實際數值，很多用戶對滑動要求挺高，\n     * 靈敏度太高沒人在乎，所以高靈敏度照舊，低靈敏度差別大一點\n     */\n    private fun @receiver:IntRange(from = 1, to = 9) Int.toRealSensitivity(): Int {\n        return when (this) {\n            1, 2, 3, 4, 5 -> this\n            6 -> 7\n            7 -> 10\n            8 -> 20\n            9 -> 40\n            else -> throw IllegalStateException(\"Invalid sensitivity value: $this\")\n        }\n    }\n}"
  },
  {
    "path": "app/src/main/java/com/yenaly/han1meviewer/ui/view/video/HMediaKernel.kt",
    "content": "package com.yenaly.han1meviewer.ui.view.video\n\nimport android.content.pm.ActivityInfo\nimport android.graphics.SurfaceTexture\nimport android.media.MediaPlayer\nimport android.media.PlaybackParams\nimport android.os.Handler\nimport android.os.Looper\nimport android.util.Log\nimport android.view.Surface\nimport android.view.TextureView\nimport androidx.annotation.OptIn\nimport androidx.media3.common.MediaItem\nimport androidx.media3.common.PlaybackException\nimport androidx.media3.common.PlaybackParameters\nimport androidx.media3.common.Player\nimport androidx.media3.common.Timeline\nimport androidx.media3.common.VideoSize\nimport androidx.media3.common.util.UnstableApi\nimport androidx.media3.datasource.DefaultDataSource\nimport androidx.media3.datasource.DefaultHttpDataSource\nimport androidx.media3.exoplayer.DefaultLoadControl\nimport androidx.media3.exoplayer.DefaultRenderersFactory\nimport androidx.media3.exoplayer.ExoPlayer\nimport androidx.media3.exoplayer.LoadControl\nimport androidx.media3.exoplayer.hls.HlsMediaSource\nimport androidx.media3.exoplayer.source.ProgressiveMediaSource\nimport androidx.media3.exoplayer.trackselection.AdaptiveTrackSelection\nimport androidx.media3.exoplayer.trackselection.DefaultTrackSelector\nimport androidx.media3.exoplayer.upstream.DefaultBandwidthMeter\nimport cn.jzvd.JZMediaInterface\nimport cn.jzvd.JZMediaSystem\nimport cn.jzvd.Jzvd\nimport com.yenaly.han1meviewer.BuildConfig\nimport com.yenaly.han1meviewer.util.AnimeShaders\nimport `is`.xyz.mpv.MPVLib\nimport kotlin.math.absoluteValue\n\n\n/**\n * @project Han1meViewer\n * @author Yenaly Liew\n * @time 2024/04/21 021 16:57\n */\nsealed interface HMediaKernel {\n    enum class Type(val clazz: Class<out JZMediaInterface>) {\n        MediaPlayer(SystemMediaKernel::class.java),\n        ExoPlayer(ExoMediaKernel::class.java),\n        MpvPlayer(MpvMediaKernel::class.java);\n\n        companion object {\n            fun fromString(name: String): Type {\n                return when (name) {\n                    MediaPlayer.name -> MediaPlayer\n                    ExoPlayer.name -> ExoPlayer\n                    MpvPlayer.name -> MpvPlayer\n                    else -> ExoPlayer\n                }\n            }\n        }\n    }\n}\n\nclass ExoMediaKernel(jzvd: Jzvd) : JZMediaInterface(jzvd), Player.Listener, HMediaKernel, IMedia {\n    companion object {\n        const val TAG = \"ExoMediaKernel\"\n    }\n\n    private var _exoPlayer: ExoPlayer? = null\n\n    /**\n     * 尽量少用，用了之后容易出bug\n     */\n    private val exoPlayer get() = _exoPlayer!!\n\n    private var prevSeek = 0L\n\n    @OptIn(UnstableApi::class)\n    override fun prepare() {\n        Log.e(TAG, \"prepare\")\n        val context = jzvd.context\n\n        release()\n//        mMediaHandlerThread = HandlerThread(\"JZVD\")\n//        mMediaHandlerThread.start()\n//        mMediaHandler = Handler(mMediaHandlerThread.looper)\n//        handler = Handler(Looper.getMainLooper())\n\n        val videoTrackSelectionFactory = AdaptiveTrackSelection.Factory()\n        val trackSelector = DefaultTrackSelector(context, videoTrackSelectionFactory)\n\n        val loadControl: LoadControl = DefaultLoadControl.Builder()\n            // .setBufferDurationsMs(360000, 600000, 1000, 5000)\n            // .setPrioritizeTimeOverSizeThresholds(false)\n            // .setTargetBufferBytes(C.LENGTH_UNSET)\n            .build()\n\n\n        val bandwidthMeter = DefaultBandwidthMeter.Builder(context).build()\n        // 2. Create the player\n        val renderersFactory = DefaultRenderersFactory(context)\n        _exoPlayer = ExoPlayer.Builder(context, renderersFactory)\n            .setTrackSelector(trackSelector)\n            .setLoadControl(loadControl)\n            .setBandwidthMeter(bandwidthMeter)\n            .build()\n        // Produces DataSource instances through which media data is loaded.\n        val dataSourceFactory = DefaultDataSource.Factory(\n            context,\n            DefaultHttpDataSource.Factory()\n                .setDefaultRequestProperties(jzvd.jzDataSource.headerMap)\n        )\n\n        val currUrl = jzvd.jzDataSource.currentUrl.toString()\n        val videoSource = if (currUrl.contains(\".m3u8\")) {\n            HlsMediaSource.Factory(dataSourceFactory)\n                .createMediaSource(MediaItem.fromUri(currUrl))\n        } else {\n            ProgressiveMediaSource.Factory(dataSourceFactory)\n                .createMediaSource(MediaItem.fromUri(currUrl))\n        }\n\n        Log.e(TAG, \"URL Link = $currUrl\")\n\n        exoPlayer.addListener(this)\n\n        val isLoop = jzvd.jzDataSource.looping\n        if (isLoop) {\n            exoPlayer.repeatMode = Player.REPEAT_MODE_ONE\n        } else {\n            exoPlayer.repeatMode = Player.REPEAT_MODE_OFF\n        }\n        exoPlayer.setMediaSource(videoSource)\n        exoPlayer.prepare()\n        exoPlayer.playWhenReady = true\n\n        val surfaceTexture = jzvd.textureView?.surfaceTexture\n        surfaceTexture?.let { exoPlayer.setVideoSurface(Surface(it)) }\n\n    }\n\n    override fun start() {\n        _exoPlayer?.playWhenReady = true\n    }\n\n    override fun onVideoSizeChanged(videoSize: VideoSize) {\n        val realWidth = videoSize.width * videoSize.pixelWidthHeightRatio\n        val realHeight = videoSize.height\n        jzvd.onVideoSizeChanged(realWidth.toInt(), realHeight)\n//        val ratio = realWidth / realHeight // > 1 橫屏， < 1 竖屏\n//        if (ratio > 1) {\n//            Jzvd.FULLSCREEN_ORIENTATION = ActivityInfo.SCREEN_ORIENTATION_SENSOR_LANDSCAPE\n//        } else {\n//            Jzvd.FULLSCREEN_ORIENTATION = ActivityInfo.SCREEN_ORIENTATION_PORTRAIT\n//        }\n    }\n\n    override fun onRenderedFirstFrame() {\n        Log.e(TAG, \"onRenderedFirstFrame\")\n    }\n\n    override fun pause() {\n        _exoPlayer?.playWhenReady = false\n    }\n\n    override fun isPlaying(): Boolean {\n        return _exoPlayer?.playWhenReady ?: false\n    }\n\n    override fun seekTo(time: Long) {\n        if (time != prevSeek) {\n            _exoPlayer?.let { exoPlayer ->\n                if (time >= exoPlayer.bufferedPosition) {\n                    jzvd.onStatePreparingPlaying()\n                }\n                exoPlayer.seekTo(time)\n                prevSeek = time\n                jzvd.seekToInAdvance = time\n            }\n        }\n    }\n\n    override fun release() {\n        if (_exoPlayer != null) { //不知道有没有妖孽\n//            val tmpHandlerThread = mMediaHandlerThread\n            val tmpMediaPlayer = exoPlayer\n            SAVED_SURFACE = null\n            tmpMediaPlayer.release() //release就不能放到主线程里，界面会卡顿\n//            tmpHandlerThread.quit()\n            _exoPlayer = null\n        }\n    }\n\n    override fun getCurrentPosition(): Long {\n        return _exoPlayer?.currentPosition ?: 0L\n    }\n\n    override fun getDuration(): Long {\n        return _exoPlayer?.duration ?: 0L\n    }\n\n    override fun setVolume(leftVolume: Float, rightVolume: Float) {\n        _exoPlayer?.volume = (leftVolume + rightVolume) / 2\n    }\n\n    override fun setSpeed(speed: Float) {\n        val playbackParams = PlaybackParameters(speed, 1.0F)\n        _exoPlayer?.playbackParameters = playbackParams\n    }\n\n    override fun onTimelineChanged(timeline: Timeline, reason: Int) {\n        Log.e(TAG, \"onTimelineChanged\")\n    }\n\n    override fun onIsLoadingChanged(isLoading: Boolean) {\n        Log.e(TAG, \"onIsLoadingChanged\")\n    }\n\n    override fun onPlayWhenReadyChanged(playWhenReady: Boolean, reason: Int) {\n        if (playWhenReady && _exoPlayer?.playbackState == Player.STATE_READY) {\n            jzvd.onStatePlaying()\n        }\n    }\n\n    override fun onPlaybackStateChanged(playbackState: Int) {\n        when (playbackState) {\n            Player.STATE_BUFFERING -> {\n                jzvd.onStatePreparingPlaying()\n                onBufferingUpdate()\n            }\n\n            Player.STATE_READY -> {\n                jzvd.onStatePlaying()\n            }\n\n            Player.STATE_ENDED -> {\n                jzvd.onCompletion()\n            }\n\n            else -> {\n                Log.e(TAG, \"onPlaybackStateChanged: $playbackState\")\n            }\n        }\n    }\n\n    override fun onPlayerError(error: PlaybackException) {\n        Log.e(TAG, \"onPlayerError: $error\")\n        mMediaHandler?.post { jzvd.onError(1000, 1000) }\n    }\n\n    override fun onPositionDiscontinuity(\n        oldPosition: Player.PositionInfo,\n        newPosition: Player.PositionInfo,\n        reason: Int,\n    ) {\n        if (reason == Player.DISCONTINUITY_REASON_SEEK) {\n            jzvd.onSeekComplete()\n        }\n    }\n\n    override fun setSurface(surface: Surface?) {\n        Log.e(TAG, \"setSurface: \", )\n        _exoPlayer?.setVideoSurface(surface)\n    }\n\n    override fun onSurfaceTextureAvailable(surface: SurfaceTexture, width: Int, height: Int) {\n        Log.d(TAG, \"onSurfaceTextureAvailable: $SAVED_SURFACE $width $height\")\n        if (SAVED_SURFACE == null) {\n            SAVED_SURFACE = surface\n            prepare()\n        } else {\n            jzvd.textureView.setSurfaceTexture(SAVED_SURFACE)\n        }\n    }\n\n    override fun onSurfaceTextureSizeChanged(st: SurfaceTexture, width: Int, height: Int) = Unit\n\n    override fun onSurfaceTextureDestroyed(surface: SurfaceTexture): Boolean = false\n\n    override fun onSurfaceTextureUpdated(surface: SurfaceTexture) = Unit\n\n\n    private fun onBufferingUpdate() {\n        _exoPlayer?.bufferedPercentage?.let { per ->\n            jzvd.setBufferProgress(per)\n//                if (per < 100) {\n//                    mMediaHandler.postDelayed(this, 300)\n//                } else {\n//                    mMediaHandler.removeCallbacks(this)\n//                }\n        }\n//            mMediaHandler.removeCallbacks(this)\n    }\n\n    override val width: Int get() = _exoPlayer?.videoSize?.width ?: 0\n    override val height: Int get() = _exoPlayer?.videoSize?.height ?: 0\n}\n\nclass SystemMediaKernel(jzvd: Jzvd) : JZMediaSystem(jzvd), HMediaKernel, IMedia {\n    // #issue-26: 有的手機長按快進會報錯，合理懷疑是不是因爲沒有加 post\n    // #issue-28: 有的平板长按快进也会报错，结果是 IllegalArgumentException，很奇怪，两次 try-catch 处理试试。\n    override fun setSpeed(speed: Float) {\n        mMediaHandler?.post {\n            try {\n                val pp = mediaPlayer.playbackParams\n                pp.speed = speed.absoluteValue\n                mediaPlayer.playbackParams = pp\n            } catch (_: IllegalArgumentException) {\n                try {\n                    val opp = PlaybackParams().setSpeed(speed.absoluteValue)\n                    mediaPlayer.playbackParams = opp\n                } catch (e: IllegalArgumentException) {\n                    e.printStackTrace()\n                }\n            }\n        }\n    }\n\n    override fun onVideoSizeChanged(mediaPlayer: MediaPlayer?, width: Int, height: Int) {\n        super.onVideoSizeChanged(mediaPlayer, width, height)\n        val ratio = width.toFloat() / height // > 1 橫屏， < 1 竖屏\n        if (ratio > 1) {\n            Jzvd.FULLSCREEN_ORIENTATION = ActivityInfo.SCREEN_ORIENTATION_SENSOR_LANDSCAPE\n        } else {\n            Jzvd.FULLSCREEN_ORIENTATION = ActivityInfo.SCREEN_ORIENTATION_PORTRAIT\n        }\n    }\n\n    // #issue-139: 部分机型暂停报错，没判空导致的\n    override fun pause() {\n        mMediaHandler?.post {\n            mediaPlayer?.pause()\n        }\n    }\n\n    // #issue-crashlytics-c8636c4bb0b8516675cbeb9e8776bf0b:\n    // 有些机器到这里可能会报空指针异常，所以加了个判断，但是不知道为什么会报空指针异常\n    override fun isPlaying(): Boolean {\n        return mediaPlayer?.isPlaying == true\n    }\n\n    override val width: Int get() = mediaPlayer?.videoWidth ?: 0\n    override val height: Int get() = mediaPlayer?.videoHeight ?: 0\n}\n\nclass MpvMediaKernel(jzvd: Jzvd) : JZMediaInterface(jzvd), HMediaKernel, IMedia {\n    companion object {\n        const val TAG = \"MpvMediaKernel\"\n    }\n\n    fun init() {\n\n        MPVLib.addObserver(mpvEventObserver)\n\n        MPVLib.setOptionString(\"vo\", \"gpu\")\n        MPVLib.setOptionString(\"profile\", \"fast\")\n        MPVLib.setOptionString(\"hwdec\", \"auto\")\n        MPVLib.setOptionString(\"msg-level\", \"all=\" + if (BuildConfig.DEBUG) \"v\" else \"warn\")\n\n        MPVLib.observeProperty(\"time-pos\", MPVLib.mpvFormat.MPV_FORMAT_DOUBLE)\n        MPVLib.observeProperty(\"duration\", MPVLib.mpvFormat.MPV_FORMAT_DOUBLE)\n        MPVLib.observeProperty(\"pause\", MPVLib.mpvFormat.MPV_FORMAT_FLAG)\n        MPVLib.observeProperty(\"playback-active\", MPVLib.mpvFormat.MPV_FORMAT_FLAG)\n        MPVLib.observeProperty(\"video-params/w\", MPVLib.mpvFormat.MPV_FORMAT_INT64)\n        MPVLib.observeProperty(\"video-params/h\", MPVLib.mpvFormat.MPV_FORMAT_INT64)\n    }\n\n    override fun prepare() {\n        init()\n        handler = Handler(Looper.getMainLooper())\n\n        val url = jzvd.jzDataSource.currentUrl.toString()\n        if (url.isEmpty()) {\n            Log.e(TAG, \"视频链接为空\")\n            return\n        }\n\n        Log.e(TAG, \"URL Link = $url\")\n        MPVLib.setOptionString(\"force-window\", \"yes\")\n        MPVLib.command(arrayOf(\"loadfile\", url))\n\n        val surfaceTexture = jzvd.textureView?.surfaceTexture\n        surfaceTexture?.let { MPVLib.attachSurface(Surface(it)) }\n    }\n\n    override fun start() {\n        MPVLib.setPropertyBoolean(\"pause\", false)\n    }\n\n    override fun pause() {\n        MPVLib.setPropertyBoolean(\"pause\", true)\n    }\n\n    override fun isPlaying(): Boolean {\n        val pause = MPVLib.getPropertyBoolean(\"pause\")\n        return !pause\n    }\n\n    override fun seekTo(time: Long) {\n        MPVLib.command(arrayOf(\"seek\", (time / 1000.0).toString(), \"absolute\"))\n    }\n\n    override fun release() {\n        Log.d(TAG, \"release\")\n        clearSuperResolution()\n        MPVLib.setPropertyBoolean(\"pause\", true)\n        MPVLib.command(arrayOf(\"loadfile\", \"\", \"replace\"))\n        MPVLib.setPropertyString(\"vo\", \"null\")\n        MPVLib.setOptionString(\"force-window\", \"no\")\n        MPVLib.detachSurface()\n        MPVLib.removeObserver(mpvEventObserver)\n        SAVED_SURFACE = null\n    }\n\n    override fun getCurrentPosition(): Long {\n        val timePosSeconds = MPVLib.getPropertyDouble(\"time-pos\")\n        return (timePosSeconds ?: 0.0).toLong() * 1000\n    }\n\n    override fun getDuration(): Long {\n        val durationSeconds = MPVLib.getPropertyDouble(\"duration\")\n        return (durationSeconds ?: 0.0).toLong() * 1000\n    }\n\n    override fun setVolume(leftVolume: Float, rightVolume: Float) {\n        val volume = (leftVolume + rightVolume) / 2 * 100\n        MPVLib.setPropertyDouble(\"volume\", volume.toDouble())\n    }\n\n    override fun setSpeed(speed: Float) {\n        MPVLib.setPropertyDouble(\"speed\", speed.toDouble())\n    }\n\n    override fun setSurface(surface: Surface?) {\n        MPVLib.attachSurface(surface)\n    }\n\n    override fun onSurfaceTextureAvailable(surfaceTexture: SurfaceTexture, width: Int, height: Int) {\n        if (SAVED_SURFACE == null) {\n            SAVED_SURFACE = surfaceTexture\n            prepare()\n        } else {\n            jzvd.textureView.setSurfaceTexture(SAVED_SURFACE)\n        }\n    }\n\n    fun updateSurFaceSize(width: Int, height: Int) {\n        Log.d(TAG, \"updateSurFaceSize ${width}x${height}\")\n        MPVLib.setPropertyString(\"android-surface-size\", \"${width}x${height}\")\n    }\n\n    override fun onSurfaceTextureSizeChanged(surfaceTexture: SurfaceTexture, width: Int, height: Int) {\n        Log.d(TAG, \"onSurfaceTextureSizeChanged ${width}x${height}\")\n        updateSurFaceSize(width, height)\n    }\n\n    override fun onSurfaceTextureDestroyed(surfaceTexture: SurfaceTexture): Boolean = false\n\n    override fun onSurfaceTextureUpdated(surfaceTexture: SurfaceTexture) {}\n\n\n    private fun clearSuperResolution() {\n        MPVLib.command(arrayOf(\"change-list\", \"glsl-shaders\", \"clr\", \"\"))\n    }\n    fun setSuperResolution(index: Int) {\n        if (index != 0) {\n            val cmd = arrayOf(\"change-list\", \"glsl-shaders\", \"set\", AnimeShaders.getShader(jzvd.context, index))\n            MPVLib.command(cmd)\n        } else {\n            clearSuperResolution()\n        }\n    }\n\n    private val mpvEventObserver = object : MPVLib.EventObserver {\n        override fun eventProperty(property: String) {\n//            Log.d(TAG, \"eventProperty: $property\")\n        }\n\n        override fun eventProperty(property: String, value: Long) {\n\n        }\n\n        override fun eventProperty(property: String, value: Boolean) {\n//            Log.d(TAG, \"eventProperty: $property $value\")\n        }\n\n        override fun eventProperty(property: String, value: String) {\n//            Log.d(TAG, \"eventProperty: $property $value\")\n        }\n\n        override fun eventProperty(property: String, value: Double) {\n//            Log.d(TAG, \"eventProperty: $property $value\")\n        }\n\n        override fun event(eventId: Int) {\n            handler.post {\n                when (eventId) {\n                    MPVLib.mpvEventId.MPV_EVENT_START_FILE -> {\n                        // 文件开始加载\n                        jzvd.onStatePreparing()\n                    }\n                    MPVLib.mpvEventId.MPV_EVENT_FILE_LOADED -> {\n                        // 文件加载成功\n                        jzvd.onPrepared()\n                    }\n                    MPVLib.mpvEventId.MPV_EVENT_PLAYBACK_RESTART -> {\n                        // 播放重新开始\n                        jzvd.onStatePlaying()\n                    }\n                    MPVLib.mpvEventId.MPV_EVENT_END_FILE -> {\n                        // 播放结束\n                        jzvd.onCompletion()\n                    }\n                    MPVLib.mpvEventId.MPV_EVENT_SHUTDOWN -> {\n                        Log.e(TAG, \"event: $eventId\")\n                    }\n                }\n            }\n        }\n    }\n\n    override val width: Int get() = MPVLib.getPropertyInt(\"video-params/w\") ?: 0\n    override val height: Int get() = MPVLib.getPropertyInt(\"video-params/h\") ?: 0\n}\n"
  },
  {
    "path": "app/src/main/java/com/yenaly/han1meviewer/ui/view/video/HanimeDataSource.kt",
    "content": "package com.yenaly.han1meviewer.ui.view.video\n\nimport cn.jzvd.JZDataSource\nimport com.yenaly.han1meviewer.ResolutionLinkMap\n\nclass HanimeDataSource : JZDataSource {\n\n    private val urlsList = mutableListOf<Map.Entry<Any?, Any?>>()\n\n    @Suppress(\"UNCHECKED_CAST\")\n    constructor(title: String, resolutionLinkMap: ResolutionLinkMap) : this() {\n        this.currentUrlIndex = 0\n        urlsList.clear()\n        this.urlsMap.also { map ->\n            map.clear()\n            resolutionLinkMap.mapValuesTo(map) { it.value.link }\n            urlsList.addAll(map.entries as Set<Map.Entry<Any?, Any?>>)\n        }\n        this.title = title\n        this.headerMap = hashMapOf()\n        this.looping = false\n        this.objects = null\n    }\n\n    override fun getKeyFromDataSource(index: Int): String? {\n        return urlsList.getOrNull(index)?.key?.toString()\n    }\n\n    override fun getValueFromLinkedMap(index: Int): Any? {\n        return urlsList.getOrNull(index)?.value\n    }\n\n    private constructor() : super(\"\")\n\n\n}"
  },
  {
    "path": "app/src/main/java/com/yenaly/han1meviewer/ui/view/video/IMedia.kt",
    "content": "package com.yenaly.han1meviewer.ui.view.video\n\ninterface IMedia {\n    val width: Int\n    val height: Int\n\n    val ratio: Float\n        get() = if (height == 0) 1f else width.toFloat() / height.toFloat()\n}"
  },
  {
    "path": "app/src/main/java/com/yenaly/han1meviewer/ui/viewmodel/AppViewModel.kt",
    "content": "package com.yenaly.han1meviewer.ui.viewmodel\n\nimport android.util.Log\nimport androidx.lifecycle.viewModelScope\nimport androidx.work.WorkManager\nimport com.google.firebase.Firebase\nimport com.google.firebase.crashlytics.crashlytics\nimport com.google.firebase.crashlytics.setCustomKeys\nimport com.yenaly.han1meviewer.FirebaseConstants\nimport com.yenaly.han1meviewer.Preferences\nimport com.yenaly.han1meviewer.logic.NetworkRepo\nimport com.yenaly.han1meviewer.logic.model.github.Latest\nimport com.yenaly.han1meviewer.logic.state.WebsiteState\nimport com.yenaly.han1meviewer.worker.HUpdateWorker\nimport com.yenaly.han1meviewer.worker.HanimeDownloadManagerV2\nimport com.yenaly.han1meviewer.worker.HanimeDownloadWorker\nimport com.yenaly.yenaly_libs.base.YenalyViewModel\nimport com.yenaly.yenaly_libs.utils.application\nimport kotlinx.coroutines.Dispatchers\nimport kotlinx.coroutines.delay\nimport kotlinx.coroutines.flow.MutableStateFlow\nimport kotlinx.coroutines.flow.asStateFlow\nimport kotlinx.coroutines.launch\n\n/**\n * @project Han1meViewer\n * @author Yenaly Liew\n * @time 2024/03/29 029 18:00\n */\nobject AppViewModel : YenalyViewModel(application), IHCsrfToken {\n\n    /**\n     * csrfToken 全局唯一，只需要在首页拉起或点击视频页时更新一下就可以了\n     */\n    override var csrfToken: String? = null\n\n    private val _versionFlow = MutableStateFlow<WebsiteState<Latest?>>(WebsiteState.Loading)\n    val versionFlow = _versionFlow.asStateFlow()\n\n    val runningWorkInfoCountFlow = MutableStateFlow(0)\n\n    init {\n        // 取消，防止每次启动都有残留的更新任务\n        WorkManager.getInstance(application).pruneWork()\n\n        viewModelScope.launch(Dispatchers.IO) {\n            // HanimeDownloadManager.init()\n            HanimeDownloadManagerV2.init()\n        }\n\n        viewModelScope.launch(Dispatchers.Main) {\n            Preferences.loginStateFlow.collect { isLogin ->\n                Log.d(\"LoginState\", \"isLogin: $isLogin\")\n                Firebase.crashlytics.setCustomKeys {\n                    key(FirebaseConstants.LOGIN_STATE, isLogin)\n                }\n            }\n        }\n\n        viewModelScope.launch(Dispatchers.Main) {\n            HUpdateWorker.collectOutput(application)\n        }\n\n        viewModelScope.launch(Dispatchers.IO) {\n            HanimeDownloadWorker.getRunningWorkInfoCount(application).collect { count ->\n                Log.d(HanimeDownloadWorker.TAG, \"getRunningWorkInfoCount: $count\")\n                runningWorkInfoCountFlow.value = count\n                Firebase.crashlytics.setCustomKeys {\n                    key(FirebaseConstants.RUNNING_DOWNLOAD_WORK_COUNT, count)\n                }\n            }\n        }\n    }\n\n    fun getLatestVersion(forceCheck: Boolean = true, delayMillis: Long = 0) {\n        viewModelScope.launch {\n            delay(delayMillis)\n            getLatestVersionSuspend(forceCheck)\n        }\n    }\n\n    private suspend fun getLatestVersionSuspend(forceCheck: Boolean = true) {\n        NetworkRepo.getLatestVersion(forceCheck).collect {\n            _versionFlow.value = it\n        }\n    }\n}"
  },
  {
    "path": "app/src/main/java/com/yenaly/han1meviewer/ui/viewmodel/CommentViewModel.kt",
    "content": "package com.yenaly.han1meviewer.ui.viewmodel\n\nimport android.app.Application\nimport androidx.lifecycle.viewModelScope\nimport com.yenaly.han1meviewer.R\nimport com.yenaly.han1meviewer.logic.NetworkRepo\nimport com.yenaly.han1meviewer.logic.model.CommentPlace\nimport com.yenaly.han1meviewer.logic.model.VideoCommentArgs\nimport com.yenaly.han1meviewer.logic.model.VideoComments\nimport com.yenaly.han1meviewer.logic.state.WebsiteState\nimport com.yenaly.han1meviewer.ui.viewmodel.AppViewModel.csrfToken\nimport com.yenaly.yenaly_libs.base.YenalyViewModel\nimport com.yenaly.yenaly_libs.utils.showShortToast\nimport kotlinx.coroutines.flow.MutableSharedFlow\nimport kotlinx.coroutines.flow.MutableStateFlow\nimport kotlinx.coroutines.flow.asSharedFlow\nimport kotlinx.coroutines.flow.asStateFlow\nimport kotlinx.coroutines.flow.update\nimport kotlinx.coroutines.launch\n\n/**\n * @project Han1meViewer\n * @author Yenaly Liew\n * @time 2022/06/28 028 14:18\n */\nclass CommentViewModel(application: Application) : YenalyViewModel(application) {\n\n    lateinit var code: String\n\n    var currentUserId: String? = null\n\n    private val _videoCommentStateFlow =\n        MutableStateFlow<WebsiteState<VideoComments>>(WebsiteState.Loading)\n    val videoCommentStateFlow = _videoCommentStateFlow.asStateFlow()\n\n    private val _videoReplyStateFlow =\n        MutableStateFlow<WebsiteState<VideoComments>>(WebsiteState.Loading)\n    val videoReplyStateFlow = _videoReplyStateFlow.asStateFlow()\n\n    private val _videoCommentFlow = MutableStateFlow(emptyList<VideoComments.VideoComment>())\n    val videoCommentFlow = _videoCommentFlow.asStateFlow()\n\n    private val _videoReplyFlow = MutableStateFlow(emptyList<VideoComments.VideoComment>())\n    val videoReplyFlow = _videoReplyFlow.asStateFlow()\n\n    private val _postCommentFlow =\n        MutableSharedFlow<WebsiteState<Unit>>(replay = 0)\n    val postCommentFlow = _postCommentFlow.asSharedFlow()\n\n    private val _postReplyFlow =\n        MutableSharedFlow<WebsiteState<Unit>>(replay = 0)\n    val postReplyFlow = _postReplyFlow.asSharedFlow()\n\n    private val _commentLikeFlow =\n        MutableSharedFlow<WebsiteState<VideoCommentArgs>>(replay = 0)\n    val commentLikeFlow = _commentLikeFlow.asSharedFlow()\n\n    fun getComment(type: String, code: String) {\n        viewModelScope.launch {\n            _videoCommentStateFlow.value = WebsiteState.Loading\n            NetworkRepo.getComments(type, code).collect { state ->\n                _videoCommentStateFlow.value = state\n                _videoCommentFlow.update { prevList ->\n                    when (state) {\n                        is WebsiteState.Success -> state.info.videoComment\n                        is WebsiteState.Loading -> emptyList()\n                        else -> prevList\n                    }\n                }\n            }\n        }\n    }\n\n    fun updateComments(comments: List<VideoComments.VideoComment>) {\n        _videoCommentFlow.update { comments }\n    }\n\n    fun getCommentReply(commentId: String) {\n        viewModelScope.launch {\n            // 每次获取评论回复时，都会重新加载\n            _videoReplyStateFlow.value = WebsiteState.Loading\n            NetworkRepo.getCommentReply(commentId).collect { state ->\n                _videoReplyStateFlow.value = state\n                _videoReplyFlow.update { prevList ->\n                    when (state) {\n                        is WebsiteState.Success -> state.info.videoComment\n                        is WebsiteState.Loading -> emptyList()\n                        else -> prevList\n                    }\n                }\n            }\n        }\n    }\n\n    fun postComment(\n        currentUserId: String,\n        targetUserId: String,\n        type: String,\n        text: String,\n    ) {\n        viewModelScope.launch {\n            NetworkRepo.postComment(csrfToken, currentUserId, targetUserId, type, text)\n                .collect(_postCommentFlow::emit)\n        }\n    }\n\n    fun postReply(\n        replyCommentId: String,\n        text: String,\n    ) {\n        viewModelScope.launch {\n            NetworkRepo.postCommentReply(csrfToken, replyCommentId, text)\n                .collect(_postReplyFlow::emit)\n        }\n    }\n\n    fun likeComment(\n        isPositive: Boolean, commentPosition: Int,\n        comment: VideoComments.VideoComment, likeCommentStatus: Boolean = false,\n        unlikeCommentStatus: Boolean = false,\n    ) = likeCommentInternal(\n        CommentPlace.COMMENT, isPositive, commentPosition,\n        comment, likeCommentStatus, unlikeCommentStatus\n    )\n\n    fun likeChildComment(\n        isPositive: Boolean, commentPosition: Int,\n        comment: VideoComments.VideoComment, likeCommentStatus: Boolean = false,\n        unlikeCommentStatus: Boolean = false,\n    ) = likeCommentInternal(\n        CommentPlace.CHILD_COMMENT, isPositive, commentPosition,\n        comment, likeCommentStatus, unlikeCommentStatus\n    )\n\n    private fun likeCommentInternal(\n        commentPlace: CommentPlace,\n        isPositive: Boolean,\n        commentPosition: Int,\n        comment: VideoComments.VideoComment,\n        likeCommentStatus: Boolean = false,\n        unlikeCommentStatus: Boolean = false,\n    ) {\n        viewModelScope.launch {\n            NetworkRepo.likeComment(\n                csrfToken,\n                commentPlace,\n                comment.post.foreignId,\n                isPositive,\n                comment.post.likeUserId,\n                comment.post.commentLikesCount ?: 0,\n                comment.post.commentLikesSum ?: 0,\n                likeCommentStatus,\n                unlikeCommentStatus,\n                commentPosition, comment\n            ).collect { argState ->\n                _commentLikeFlow.emit(argState)\n                if (argState is WebsiteState.Success) {\n                    when (commentPlace) {\n                        CommentPlace.COMMENT -> _videoCommentFlow.update { prevList ->\n                            prevList.toMutableList().apply {\n                                this[commentPosition] =\n                                    this[commentPosition].handleCommentLike(argState.info)\n                            }\n                        }\n\n                        CommentPlace.CHILD_COMMENT -> _videoReplyFlow.update { prevList ->\n                            prevList.toMutableList().apply {\n                                this[commentPosition] =\n                                    this[commentPosition].handleCommentLike(argState.info)\n                            }\n                        }\n                    }\n                }\n            }\n        }\n    }\n\n    private fun VideoComments.VideoComment.handleCommentLike(\n        args: VideoCommentArgs,\n    ) = if (args.isPositive) {\n        this.incLikesCount(cancel = post.likeCommentStatus)\n    } else {\n        this.decLikesCount(cancel = post.unlikeCommentStatus)\n    }\n\n    fun handleCommentLike(args: VideoCommentArgs) {\n        if (args.isPositive) {\n            if (args.comment.post.likeCommentStatus) {\n                showShortToast(R.string.cancel_thumb_up_success)\n            } else {\n                showShortToast(R.string.thumb_up_success)\n            }\n        } else {\n            if (args.comment.post.unlikeCommentStatus) {\n                showShortToast(R.string.cancel_thumb_down_success)\n            } else {\n                showShortToast(R.string.thumb_down_success)\n            }\n        }\n    }\n}"
  },
  {
    "path": "app/src/main/java/com/yenaly/han1meviewer/ui/viewmodel/DownloadViewModel.kt",
    "content": "package com.yenaly.han1meviewer.ui.viewmodel\n\nimport android.app.Application\nimport androidx.annotation.IdRes\nimport androidx.lifecycle.viewModelScope\nimport com.yenaly.han1meviewer.R\nimport com.yenaly.han1meviewer.logic.DatabaseRepo\nimport com.yenaly.han1meviewer.logic.entity.download.HanimeDownloadEntity\nimport com.yenaly.han1meviewer.logic.entity.download.VideoWithCategories\nimport com.yenaly.yenaly_libs.base.YenalyViewModel\nimport kotlinx.coroutines.Dispatchers\nimport kotlinx.coroutines.flow.MutableStateFlow\nimport kotlinx.coroutines.flow.asStateFlow\nimport kotlinx.coroutines.flow.catch\nimport kotlinx.coroutines.flow.flowOn\nimport kotlinx.coroutines.launch\n\n/**\n * @project Han1meViewer\n * @author Yenaly Liew\n * @time 2022/08/02 002 12:05\n */\nclass DownloadViewModel(application: Application) : YenalyViewModel(application) {\n\n    @IdRes\n    var currentSortOptionId = R.id.sm_sort_by_date_descending\n\n    private val _downloaded = MutableStateFlow(mutableListOf<VideoWithCategories>())\n    val downloaded = _downloaded.asStateFlow()\n\n    fun loadAllDownloadingHanime() =\n        DatabaseRepo.HanimeDownload.loadAllDownloadingHanime()\n            .catch { e -> e.printStackTrace() }\n            .flowOn(Dispatchers.IO)\n\n    fun loadUpdating() =\n        DatabaseRepo.HanimeDownload.loadloadUpdating()\n            .catch { e -> e.printStackTrace() }\n            .flowOn(Dispatchers.IO)\n\n    fun loadAllDownloadedHanime(\n        sortedBy: HanimeDownloadEntity.SortedBy = HanimeDownloadEntity.SortedBy.ID,\n        ascending: Boolean = false,\n    ) {\n        viewModelScope.launch {\n            DatabaseRepo.HanimeDownload.loadAllDownloadedHanime(sortedBy, ascending)\n                .catch { e -> e.printStackTrace() }\n                .flowOn(Dispatchers.IO)\n                .collect {\n                    _downloaded.value = it\n                }\n        }\n    }\n\n    fun updateDownloadHanime(entity: HanimeDownloadEntity) {\n        viewModelScope.launch {\n            DatabaseRepo.HanimeDownload.update(entity)\n        }\n    }\n\n    fun deleteDownloadHanimeBy(videoCode: String, quality: String) {\n        viewModelScope.launch(Dispatchers.IO) {\n            DatabaseRepo.HanimeDownload.delete(videoCode, quality)\n        }\n    }\n}"
  },
  {
    "path": "app/src/main/java/com/yenaly/han1meviewer/ui/viewmodel/IHCsrfToken.kt",
    "content": "package com.yenaly.han1meviewer.ui.viewmodel\n\ninterface IHCsrfToken {\n    var csrfToken: String?\n}"
  },
  {
    "path": "app/src/main/java/com/yenaly/han1meviewer/ui/viewmodel/MainViewModel.kt",
    "content": "package com.yenaly.han1meviewer.ui.viewmodel\n\nimport android.app.Application\nimport android.util.Log\nimport androidx.lifecycle.viewModelScope\nimport com.yenaly.han1meviewer.logic.DatabaseRepo\nimport com.yenaly.han1meviewer.logic.NetworkRepo\nimport com.yenaly.han1meviewer.logic.entity.WatchHistoryEntity\nimport com.yenaly.han1meviewer.logic.model.HomePage\nimport com.yenaly.han1meviewer.logic.state.WebsiteState\nimport com.yenaly.han1meviewer.ui.viewmodel.AppViewModel.csrfToken\nimport com.yenaly.yenaly_libs.base.YenalyViewModel\nimport kotlinx.coroutines.Dispatchers\nimport kotlinx.coroutines.flow.MutableStateFlow\nimport kotlinx.coroutines.flow.asStateFlow\nimport kotlinx.coroutines.flow.catch\nimport kotlinx.coroutines.flow.flowOn\nimport kotlinx.coroutines.launch\n\n/**\n * @project Hanime1\n * @author Yenaly Liew\n * @time 2022/06/08 008 17:35\n */\nclass MainViewModel(application: Application) : YenalyViewModel(application) {\n\n    private val _homePageFlow =\n        MutableStateFlow<WebsiteState<HomePage>>(WebsiteState.Loading)\n    val homePageFlow = _homePageFlow.asStateFlow()\n\n    fun getHomePage() {\n        viewModelScope.launch {\n            NetworkRepo.getHomePage().collect { homePage ->\n                if (homePage is WebsiteState.Success) {\n                    csrfToken = homePage.info.csrfToken\n                }\n                _homePageFlow.value = homePage\n            }\n        }\n    }\n\n    fun deleteWatchHistory(history: WatchHistoryEntity) {\n        viewModelScope.launch(Dispatchers.IO) {\n            DatabaseRepo.WatchHistory.delete(history)\n            Log.d(\"delete_watch_hty\", \"$history DONE!\")\n        }\n    }\n\n    fun deleteAllWatchHistories() {\n        viewModelScope.launch(Dispatchers.IO) {\n            DatabaseRepo.WatchHistory.deleteAll()\n            Log.d(\"del_all_watch_hty\", \"DONE!\")\n        }\n    }\n\n    fun loadAllWatchHistories() =\n        DatabaseRepo.WatchHistory.loadAll()\n            .catch { e -> e.printStackTrace() }\n            .flowOn(Dispatchers.IO)\n}"
  },
  {
    "path": "app/src/main/java/com/yenaly/han1meviewer/ui/viewmodel/MyListViewModel.kt",
    "content": "package com.yenaly.han1meviewer.ui.viewmodel\n\nimport android.app.Application\nimport com.yenaly.han1meviewer.ui.viewmodel.mylist.FavSubViewModel\nimport com.yenaly.han1meviewer.ui.viewmodel.mylist.PlaylistSubViewModel\nimport com.yenaly.han1meviewer.ui.viewmodel.mylist.SubscriptionSubViewModel\nimport com.yenaly.han1meviewer.ui.viewmodel.mylist.WatchLaterSubViewModel\nimport com.yenaly.yenaly_libs.base.YenalyViewModel\n\n/**\n * @project Han1meViewer\n * @author Yenaly Liew\n * @time 2022/07/04 004 22:46\n */\nclass MyListViewModel(application: Application) : YenalyViewModel(application) {\n\n    val playlist by sub<PlaylistSubViewModel>()\n    val watchLater by sub<WatchLaterSubViewModel>()\n    val fav by sub<FavSubViewModel>()\n    val subscription by sub<SubscriptionSubViewModel>()\n}\n"
  },
  {
    "path": "app/src/main/java/com/yenaly/han1meviewer/ui/viewmodel/PreviewCommentPrefetcher.kt",
    "content": "package com.yenaly.han1meviewer.ui.viewmodel\n\nimport android.util.Log\nimport androidx.annotation.IntDef\nimport com.yenaly.han1meviewer.logic.model.VideoComments\nimport com.yenaly.yenaly_libs.utils.application\n\n/**\n * 连通 [PreviewActivity] 和 [PreviewCommentActivity] 的预取器\n */\nclass PreviewCommentPrefetcher {\n\n    @IntDef(flag = true, value = [Scope.PREVIEW_ACTIVITY, Scope.PREVIEW_COMMENT_ACTIVITY])\n    annotation class Scope {\n        companion object {\n            const val PREVIEW_ACTIVITY = 1\n            const val PREVIEW_COMMENT_ACTIVITY = 1 shl 1\n        }\n    }\n\n    companion object {\n        private const val TAG = \"PreviewCommentPrefetcher\"\n\n        private var prefetcher: PreviewCommentPrefetcher? = null\n\n        fun here(): PreviewCommentPrefetcher {\n            return prefetcher ?: PreviewCommentPrefetcher().also { prefetcher = it }\n        }\n\n        fun bye(@Scope scope: Int) {\n            prefetcher?.also {\n                it.activityMask = it.activityMask and scope.inv()\n                if (it.activityMask == 0) {\n                    prefetcher = null\n                    Log.i(TAG, \"bye executed successfully\")\n                } else {\n                    if (it.activityMask and Scope.PREVIEW_ACTIVITY != 0) {\n                        Log.i(\n                            TAG, \"bye executed failed: \" +\n                                    \"prefetcher is still alive cuz of PreviewActivity\"\n                        )\n                    }\n                    if (it.activityMask and Scope.PREVIEW_COMMENT_ACTIVITY != 0) {\n                        Log.i(\n                            TAG, \"bye executed failed: \" +\n                                    \"prefetcher is still alive cuz of PreviewCommentActivity\"\n                        )\n                    }\n                }\n            }\n        }\n    }\n\n    private var activityMask = 0\n\n    private val commentViewModel = CommentViewModel(application)\n\n    val commentFlow get() = commentViewModel.videoCommentFlow\n\n    fun tag(@Scope scope: Int) {\n        activityMask = activityMask or scope\n    }\n\n    fun fetch(type: String, code: String) {\n        commentViewModel.getComment(type, code)\n    }\n\n    fun update(comments: List<VideoComments.VideoComment>) {\n        commentViewModel.updateComments(comments)\n    }\n}"
  },
  {
    "path": "app/src/main/java/com/yenaly/han1meviewer/ui/viewmodel/PreviewViewModel.kt",
    "content": "package com.yenaly.han1meviewer.ui.viewmodel\n\nimport android.app.Application\nimport androidx.lifecycle.viewModelScope\nimport com.yenaly.han1meviewer.logic.NetworkRepo\nimport com.yenaly.han1meviewer.logic.model.HanimePreview\nimport com.yenaly.han1meviewer.logic.state.WebsiteState\nimport com.yenaly.yenaly_libs.base.YenalyViewModel\nimport kotlinx.coroutines.flow.MutableStateFlow\nimport kotlinx.coroutines.flow.asStateFlow\nimport kotlinx.coroutines.launch\n\n/**\n * @project Hanime1\n * @author Yenaly Liew\n * @time 2022/06/23 023 16:47\n */\nclass PreviewViewModel(application: Application) : YenalyViewModel(application) {\n\n    private val _previewFlow =\n        MutableStateFlow<WebsiteState<HanimePreview>>(WebsiteState.Loading)\n    val previewFlow = _previewFlow.asStateFlow()\n\n    fun getHanimePreview(date: String) {\n        viewModelScope.launch {\n            NetworkRepo.getHanimePreview(date).collect { preview ->\n                _previewFlow.value = preview\n            }\n        }\n    }\n}"
  },
  {
    "path": "app/src/main/java/com/yenaly/han1meviewer/ui/viewmodel/SearchViewModel.kt",
    "content": "package com.yenaly.han1meviewer.ui.viewmodel\n\nimport android.app.Application\nimport android.util.Log\nimport android.util.SparseArray\nimport androidx.lifecycle.viewModelScope\nimport com.yenaly.han1meviewer.logic.DatabaseRepo\nimport com.yenaly.han1meviewer.logic.NetworkRepo\nimport com.yenaly.han1meviewer.logic.entity.SearchHistoryEntity\nimport com.yenaly.han1meviewer.logic.model.HanimeInfo\nimport com.yenaly.han1meviewer.logic.model.SearchOption\nimport com.yenaly.han1meviewer.logic.state.PageLoadingState\nimport com.yenaly.han1meviewer.util.loadAssetAs\nimport com.yenaly.yenaly_libs.base.YenalyViewModel\nimport com.yenaly.yenaly_libs.utils.unsafeLazy\nimport kotlinx.coroutines.Dispatchers\nimport kotlinx.coroutines.flow.MutableStateFlow\nimport kotlinx.coroutines.flow.asStateFlow\nimport kotlinx.coroutines.flow.flowOn\nimport kotlinx.coroutines.flow.getAndUpdate\nimport kotlinx.coroutines.flow.update\nimport kotlinx.coroutines.launch\n\n/**\n * @project Hanime1\n * @author Yenaly Liew\n * @time 2022/06/13 013 22:29\n */\nclass SearchViewModel(application: Application) : YenalyViewModel(application) {\n\n    var page: Int = 1\n    var query: String? = null\n\n    // START: Use in [ChildCommentPopupFragment.kt]\n\n    var genre: String? = null\n    var sort: String? = null\n    var broad: Boolean = false\n    var year: Int? = null\n    var month: Int? = null\n    var duration: String? = null\n\n    var subscriptionBrand: String? = null\n\n    var tagMap = SparseArray<Set<SearchOption>>()\n    var brandMap = SparseArray<Set<SearchOption>>()\n\n    // END: Use in [ChildCommentPopupFragment.kt]\n\n    // START: Use in [SearchOptionsPopupFragment.kt]\n\n    val genres by unsafeLazy {\n        loadAssetAs<List<SearchOption>>(\"search_options/genre.json\").orEmpty()\n    }\n\n    val tags by unsafeLazy {\n        loadAssetAs<Map<String, List<SearchOption>>>(\"search_options/tags.json\").orEmpty()\n    }\n\n    val brands by unsafeLazy {\n        loadAssetAs<List<SearchOption>>(\"search_options/brands.json\").orEmpty()\n    }\n\n    val sortOptions by unsafeLazy {\n        loadAssetAs<List<SearchOption>>(\"search_options/sort_option.json\").orEmpty()\n    }\n\n    val durations by unsafeLazy {\n        loadAssetAs<List<SearchOption>>(\"search_options/duration.json\").orEmpty()\n    }\n\n    // END: Use in [SearchOptionsPopupFragment.kt]\n\n    private val _searchStateFlow =\n        MutableStateFlow<PageLoadingState<List<HanimeInfo>>>(PageLoadingState.Loading)\n    val searchStateFlow = _searchStateFlow.asStateFlow()\n\n    private val _searchFlow = MutableStateFlow(emptyList<HanimeInfo>())\n    val searchFlow = _searchFlow.asStateFlow()\n\n    fun clearHanimeSearchResult() = _searchStateFlow.update { PageLoadingState.Loading }\n\n    fun getHanimeSearchResult(\n        page: Int, query: String?, genre: String?,\n        sort: String?, broad: Boolean, year: Int?, month: Int?,\n        duration: String?, tags: Set<String>, brands: Set<String>,\n    ) {\n        viewModelScope.launch {\n            var date: String? = null\n            if (year != null && month != null) {\n                date = \"$year 年 $month 月\"\n            }\n            if (year != null && month == null) {\n                date = \"$year 年\"\n            }\n            NetworkRepo.getHanimeSearchResult(\n                page, query, genre,\n                sort, broad, date,\n                duration, tags, brands\n            ).collect { state ->\n                val prev = _searchStateFlow.getAndUpdate { state }\n                if (prev is PageLoadingState.Loading) _searchFlow.value = emptyList()\n                _searchFlow.update { prevList ->\n                    when (state) {\n                        is PageLoadingState.Success -> prevList + state.info\n                        is PageLoadingState.Loading -> emptyList()\n                        else -> prevList\n                    }\n                }\n            }\n        }\n    }\n\n    fun insertSearchHistory(history: SearchHistoryEntity) {\n        viewModelScope.launch(Dispatchers.IO) {\n            DatabaseRepo.SearchHistory.insert(history)\n            Log.d(\"insert_search_hty\", \"$history DONE!\")\n        }\n    }\n\n    fun deleteSearchHistory(history: SearchHistoryEntity) {\n        viewModelScope.launch(Dispatchers.IO) {\n            DatabaseRepo.SearchHistory.delete(history)\n            Log.d(\"delete_search_hty\", \"$history DONE!\")\n        }\n    }\n\n    @JvmOverloads\n    fun loadAllSearchHistories(keyword: String? = null) =\n        DatabaseRepo.SearchHistory.loadAll(keyword).flowOn(Dispatchers.IO)\n\n    fun deleteSearchHistoryByKeyword(query: String) {\n        viewModelScope.launch(Dispatchers.IO) {\n            DatabaseRepo.SearchHistory.deleteByKeyword(query)\n            Log.d(\"delete_search_hty\", \"$query DONE!\")\n        }\n    }\n}"
  },
  {
    "path": "app/src/main/java/com/yenaly/han1meviewer/ui/viewmodel/SettingsViewModel.kt",
    "content": "package com.yenaly.han1meviewer.ui.viewmodel\n\nimport android.app.Application\nimport androidx.lifecycle.viewModelScope\nimport com.yenaly.han1meviewer.logic.DatabaseRepo\nimport com.yenaly.han1meviewer.logic.entity.HKeyframeEntity\nimport com.yenaly.yenaly_libs.base.YenalyViewModel\nimport kotlinx.coroutines.Dispatchers\nimport kotlinx.coroutines.flow.flowOn\nimport kotlinx.coroutines.launch\n\n/**\n * @project Han1meViewer\n * @author Yenaly Liew\n * @time 2022/07/01 001 13:40\n */\nclass SettingsViewModel(application: Application) : YenalyViewModel(application) {\n\n    fun loadAllHKeyframes(keyword: String? = null) =\n        DatabaseRepo.HKeyframe.loadAll(keyword).flowOn(Dispatchers.IO)\n\n    fun removeHKeyframe(videoCode: String, keyframe: HKeyframeEntity.Keyframe) {\n        viewModelScope.launch(Dispatchers.IO) {\n            DatabaseRepo.HKeyframe.removeKeyframe(videoCode, keyframe)\n        }\n    }\n\n\n    fun modifyHKeyframe(\n        videoCode: String,\n        oldKeyframe: HKeyframeEntity.Keyframe, keyframe: HKeyframeEntity.Keyframe,\n    ) {\n        viewModelScope.launch(Dispatchers.IO) {\n            DatabaseRepo.HKeyframe.modifyKeyframe(videoCode, oldKeyframe, keyframe)\n        }\n    }\n\n    fun insertHKeyframes(entity: HKeyframeEntity) {\n        viewModelScope.launch(Dispatchers.IO) {\n            DatabaseRepo.HKeyframe.insert(entity)\n        }\n    }\n\n    fun deleteHKeyframes(entity: HKeyframeEntity) {\n        viewModelScope.launch(Dispatchers.IO) {\n            DatabaseRepo.HKeyframe.delete(entity)\n        }\n    }\n\n    fun updateHKeyframes(entity: HKeyframeEntity) {\n        viewModelScope.launch(Dispatchers.IO) {\n            DatabaseRepo.HKeyframe.update(entity)\n        }\n    }\n\n    fun loadAllSharedHKeyframes() =\n        DatabaseRepo.HKeyframe.loadAllShared().flowOn(Dispatchers.IO)\n}"
  },
  {
    "path": "app/src/main/java/com/yenaly/han1meviewer/ui/viewmodel/VideoViewModel.kt",
    "content": "package com.yenaly.han1meviewer.ui.viewmodel\n\nimport android.app.Application\nimport android.util.Log\nimport androidx.lifecycle.viewModelScope\nimport com.yenaly.han1meviewer.EMPTY_STRING\nimport com.yenaly.han1meviewer.HCacheManager\nimport com.yenaly.han1meviewer.Preferences\nimport com.yenaly.han1meviewer.R\nimport com.yenaly.han1meviewer.logic.DatabaseRepo\nimport com.yenaly.han1meviewer.logic.NetworkRepo\nimport com.yenaly.han1meviewer.logic.entity.HKeyframeEntity\nimport com.yenaly.han1meviewer.logic.entity.WatchHistoryEntity\nimport com.yenaly.han1meviewer.logic.entity.download.HanimeDownloadEntity\nimport com.yenaly.han1meviewer.logic.model.HanimeVideo\nimport com.yenaly.han1meviewer.logic.state.VideoLoadingState\nimport com.yenaly.han1meviewer.logic.state.WebsiteState\nimport com.yenaly.han1meviewer.ui.viewmodel.AppViewModel.csrfToken\nimport com.yenaly.yenaly_libs.base.YenalyViewModel\nimport kotlinx.coroutines.Dispatchers\nimport kotlinx.coroutines.flow.MutableSharedFlow\nimport kotlinx.coroutines.flow.MutableStateFlow\nimport kotlinx.coroutines.flow.asSharedFlow\nimport kotlinx.coroutines.flow.asStateFlow\nimport kotlinx.coroutines.flow.flowOn\nimport kotlinx.coroutines.flow.map\nimport kotlinx.coroutines.flow.update\nimport kotlinx.coroutines.launch\nimport kotlin.math.abs\n\n/**\n * @project Hanime1\n * @author Yenaly Liew\n * @time 2022/06/17 017 19:01\n */\nclass VideoViewModel(application: Application) : YenalyViewModel(application) {\n\n    companion object {\n        /**\n         * 最小的 HKeyframe 保存間隔，暫定 5s\n         */\n        const val MIN_H_KEYFRAME_SAVE_INTERVAL = 5_000 // ms\n    }\n\n    var videoCode: String = EMPTY_STRING\n        set(value) {\n            field = value\n            // 在這裏初始化所有需要videoCode的方法\n            getHanimeVideo(value)\n        }\n\n    var fromDownload = false\n\n    var hKeyframes: HKeyframeEntity? = null\n\n    private val _hanimeVideoStateFlow =\n        MutableStateFlow<VideoLoadingState<HanimeVideo>>(VideoLoadingState.Loading)\n    val hanimeVideoStateFlow = _hanimeVideoStateFlow.asStateFlow()\n\n    private val _hanimeVideoFlow = MutableStateFlow<HanimeVideo?>(null)\n    val hanimeVideoFlow = _hanimeVideoFlow.asStateFlow()\n\n    fun getHanimeVideo(videoCode: String) {\n        viewModelScope.launch {\n            val flow = if (fromDownload) {\n                HCacheManager.loadHanimeVideoInfo(videoCode).map { hv ->\n                    if (hv == null) {\n                        VideoLoadingState.NoContent\n                    } else {\n                        VideoLoadingState.Success(hv)\n                    }\n                }\n            } else {\n                NetworkRepo.getHanimeVideo(videoCode)\n            }\n            flow.collect { state ->\n                _hanimeVideoStateFlow.value = state\n                if (state is VideoLoadingState.Success) {\n                    _hanimeVideoFlow.update { state.info }\n                    csrfToken = state.info.csrfToken\n                }\n            }\n        }\n    }\n\n    private val _addToFavVideoFlow = MutableSharedFlow<WebsiteState<Boolean>>()\n    val addToFavVideoFlow = _addToFavVideoFlow.asSharedFlow()\n\n    private val _loadDownloadedFlow = MutableSharedFlow<HanimeDownloadEntity?>()\n    val loadDownloadedFlow = _loadDownloadedFlow.asSharedFlow()\n\n    fun addToFavVideo(\n        videoCode: String,\n        currentUserId: String?,\n    ) = modifyFavVideoInternal(videoCode, likeStatus = false, currentUserId)\n\n    fun removeFromFavVideo(\n        videoCode: String,\n        currentUserId: String?,\n    ) = modifyFavVideoInternal(videoCode, likeStatus = true, currentUserId)\n\n    private fun modifyFavVideoInternal(\n        videoCode: String,\n        likeStatus: Boolean,\n        currentUserId: String?,\n    ) {\n        viewModelScope.launch {\n            NetworkRepo.addToMyFavVideo(\n                videoCode, likeStatus, currentUserId, csrfToken\n            ).collect { state ->\n                _addToFavVideoFlow.emit(state)\n                if (likeStatus) {\n                    // 代表移除喜爱\n                    _hanimeVideoFlow.update { it?.decFavTime() }\n                } else {\n                    // 代表添加喜爱\n                    _hanimeVideoFlow.update { it?.incFavTime() }\n                }\n            }\n        }\n    }\n\n    private val _modifyMyListFlow = MutableSharedFlow<WebsiteState<Int>>()\n    val modifyMyListFlow = _modifyMyListFlow.asSharedFlow()\n\n    fun modifyMyList(\n        listCode: String,\n        videoCode: String,\n        isChecked: Boolean,\n        position: Int,\n    ) {\n        viewModelScope.launch {\n            NetworkRepo.addToMyList(listCode, videoCode, isChecked, position, csrfToken).collect {\n                _modifyMyListFlow.emit(it)\n                _hanimeVideoFlow.update { prev ->\n                    val myList = prev?.myList?.myListInfo.orEmpty().toMutableList()\n                    myList[position] = myList[position].copy(isSelected = isChecked)\n                    prev?.copy(myList = prev.myList?.copy(myListInfo = myList))\n                }\n            }\n        }\n    }\n\n    fun insertWatchHistory(history: WatchHistoryEntity) {\n        viewModelScope.launch(Dispatchers.IO) {\n            DatabaseRepo.WatchHistory.insert(history)\n            Log.d(\"insert_watch_hty\", \"$history DONE!\")\n        }\n    }\n\n    fun insertWatchHistoryWithCover(history: WatchHistoryEntity) {\n        viewModelScope.launch(Dispatchers.IO) {\n            DatabaseRepo.WatchHistory.insert(history)\n        }\n    }\n\n    fun findDownloadedHanime(videoCode: String) {\n        viewModelScope.launch(Dispatchers.IO) {\n            val info = DatabaseRepo.HanimeDownload.find(videoCode)\n            _loadDownloadedFlow.emit(info)\n        }\n    }\n\n    // true代表已关注成功，false代表取消关注成功\n    private val _subscribeArtistFlow = MutableSharedFlow<WebsiteState<Boolean>>()\n    val subscribeArtistFlow = _subscribeArtistFlow.asSharedFlow()\n\n    fun subscribeArtist(\n        userId: String,\n        artistId: String,\n    ) {\n        viewModelScope.launch {\n            NetworkRepo.subscribeArtist(csrfToken, userId, artistId, true).collect { state ->\n                _subscribeArtistFlow.emit(state)\n                if (state is WebsiteState.Success) {\n                    _hanimeVideoFlow.update {\n                        it?.copy(artist = it.artist?.copy(post = it.artist.post?.copy(isSubscribed = true)))\n                    }\n                }\n            }\n        }\n    }\n\n    fun unsubscribeArtist(\n        userId: String,\n        artistId: String,\n    ) {\n        viewModelScope.launch {\n            NetworkRepo.subscribeArtist(csrfToken, userId, artistId, false).collect { state ->\n                _subscribeArtistFlow.emit(state)\n                if (state is WebsiteState.Success) {\n                    _hanimeVideoFlow.update {\n                        it?.copy(artist = it.artist?.copy(post = it.artist.post?.copy(isSubscribed = false)))\n                    }\n                }\n            }\n        }\n    }\n\n    // boolean: 成功 or 失敗，String: 提示信息\n    private val _modifyHKeyframeFlow = MutableSharedFlow<Pair<Boolean, String>>()\n    val modifyHKeyframeFlow = _modifyHKeyframeFlow.asSharedFlow()\n\n    fun observeKeyframe(videoCode: String) = if (Preferences.hKeyframesEnable)\n        DatabaseRepo.HKeyframe.observe(videoCode).flowOn(Dispatchers.IO) else null\n\n    fun appendHKeyframe(videoCode: String, title: String, hKeyframe: HKeyframeEntity.Keyframe) {\n        viewModelScope.launch(Dispatchers.IO) {\n            run {\n                this@VideoViewModel.hKeyframes?.keyframes?.forEach { keyframeInDb ->\n                    if (abs(keyframeInDb.position - hKeyframe.position) < MIN_H_KEYFRAME_SAVE_INTERVAL) {\n                        Log.d(\"append_hkeyframe\", \"time conflict: $keyframeInDb\")\n                        _modifyHKeyframeFlow.emit(\n                            false to application.getString(\n                                R.string.interval_must_greater_than_d,\n                                MIN_H_KEYFRAME_SAVE_INTERVAL / 1_000L\n                            )\n                        )\n                        return@run\n                    }\n                }\n                DatabaseRepo.HKeyframe.appendKeyframe(videoCode, title, hKeyframe)\n                Log.d(\"append_hkeyframe\", \"$hKeyframe DONE!\")\n                _modifyHKeyframeFlow.emit(true to application.getString(R.string.add_success))\n            }\n        }\n    }\n\n    fun removeHKeyframe(videoCode: String, hKeyframe: HKeyframeEntity.Keyframe) {\n        viewModelScope.launch(Dispatchers.IO) {\n            DatabaseRepo.HKeyframe.removeKeyframe(videoCode, hKeyframe)\n            Log.d(\"remove_hkeyframe\", \"$hKeyframe DONE!\")\n            _modifyHKeyframeFlow.emit(true to application.getString(R.string.delete_success))\n        }\n    }\n\n    fun modifyHKeyframe(\n        videoCode: String,\n        oldKeyframe: HKeyframeEntity.Keyframe, keyframe: HKeyframeEntity.Keyframe,\n    ) {\n        viewModelScope.launch {\n            DatabaseRepo.HKeyframe.modifyKeyframe(videoCode, oldKeyframe, keyframe)\n            Log.d(\"modify_hkeyframe\", \"$keyframe DONE!\")\n            _modifyHKeyframeFlow.emit(true to application.getString(R.string.modify_success))\n        }\n    }\n}"
  },
  {
    "path": "app/src/main/java/com/yenaly/han1meviewer/ui/viewmodel/mylist/FavSubViewModel.kt",
    "content": "package com.yenaly.han1meviewer.ui.viewmodel.mylist\n\nimport android.app.Application\nimport androidx.lifecycle.viewModelScope\nimport com.yenaly.han1meviewer.logic.NetworkRepo\nimport com.yenaly.han1meviewer.logic.model.HanimeInfo\nimport com.yenaly.han1meviewer.logic.model.MyListItems\nimport com.yenaly.han1meviewer.logic.model.MyListType\nimport com.yenaly.han1meviewer.logic.state.PageLoadingState\nimport com.yenaly.han1meviewer.logic.state.WebsiteState\nimport com.yenaly.han1meviewer.ui.viewmodel.AppViewModel.csrfToken\nimport com.yenaly.yenaly_libs.base.YenalyViewModel\nimport kotlinx.coroutines.flow.MutableSharedFlow\nimport kotlinx.coroutines.flow.MutableStateFlow\nimport kotlinx.coroutines.flow.asSharedFlow\nimport kotlinx.coroutines.flow.asStateFlow\nimport kotlinx.coroutines.flow.getAndUpdate\nimport kotlinx.coroutines.flow.update\nimport kotlinx.coroutines.launch\n\nclass FavSubViewModel(application: Application) : YenalyViewModel(application) {\n\n    var favVideoPage = 1\n\n    private val _favVideoStateFlow: MutableStateFlow<PageLoadingState<MyListItems<HanimeInfo>>> =\n        MutableStateFlow(PageLoadingState.Loading)\n    val favVideoStateFlow = _favVideoStateFlow.asStateFlow()\n\n    private val _favVideoFlow = MutableStateFlow(emptyList<HanimeInfo>())\n    val favVideoFlow = _favVideoFlow.asStateFlow()\n\n    fun getMyFavVideoItems(page: Int) {\n        viewModelScope.launch {\n            NetworkRepo.getMyListItems(page, MyListType.FAV_VIDEO).collect { state ->\n                val prev = _favVideoStateFlow.getAndUpdate { state }\n                if (prev is PageLoadingState.Loading) _favVideoFlow.value = emptyList()\n                _favVideoFlow.update { prevList ->\n                    when (state) {\n                        is PageLoadingState.Success -> prevList + state.info.hanimeInfo\n                        is PageLoadingState.Loading -> emptyList()\n                        else -> prevList\n                    }\n                }\n            }\n        }\n    }\n\n    private val _deleteMyFavVideoFlow =\n        MutableSharedFlow<WebsiteState<Int>>()\n    val deleteMyFavVideoFlow = _deleteMyFavVideoFlow.asSharedFlow()\n\n    fun deleteMyFavVideo(videoCode: String, position: Int) {\n        viewModelScope.launch {\n            NetworkRepo.deleteMyListItems(\n                MyListType.FAV_VIDEO, videoCode,\n                position, csrfToken\n            ).collect {\n                _deleteMyFavVideoFlow.emit(it)\n                _favVideoFlow.update { list ->\n                    if (it is WebsiteState.Success) {\n                        list.toMutableList().apply { removeAt(position) }\n                    } else list\n                }\n            }\n        }\n    }\n\n    fun clearMyListItems() {\n        _favVideoStateFlow.value = PageLoadingState.Loading\n    }\n}"
  },
  {
    "path": "app/src/main/java/com/yenaly/han1meviewer/ui/viewmodel/mylist/PlaylistSubViewModel.kt",
    "content": "package com.yenaly.han1meviewer.ui.viewmodel.mylist\n\nimport android.app.Application\nimport androidx.lifecycle.viewModelScope\nimport com.yenaly.han1meviewer.EMPTY_STRING\nimport com.yenaly.han1meviewer.logic.NetworkRepo\nimport com.yenaly.han1meviewer.logic.model.HanimeInfo\nimport com.yenaly.han1meviewer.logic.model.ModifiedPlaylistArgs\nimport com.yenaly.han1meviewer.logic.model.MyListItems\nimport com.yenaly.han1meviewer.logic.model.Playlists\nimport com.yenaly.han1meviewer.logic.state.PageLoadingState\nimport com.yenaly.han1meviewer.logic.state.WebsiteState\nimport com.yenaly.han1meviewer.ui.viewmodel.AppViewModel.csrfToken\nimport com.yenaly.yenaly_libs.base.YenalyViewModel\nimport kotlinx.coroutines.flow.MutableSharedFlow\nimport kotlinx.coroutines.flow.MutableStateFlow\nimport kotlinx.coroutines.flow.asSharedFlow\nimport kotlinx.coroutines.flow.asStateFlow\nimport kotlinx.coroutines.flow.getAndUpdate\nimport kotlinx.coroutines.flow.update\nimport kotlinx.coroutines.launch\n\nclass PlaylistSubViewModel(application: Application) : YenalyViewModel(application) {\n\n    var playlistPage = 1\n    var playlistCode: String? = null\n    var playlistTitle: String? = null\n    var playlistDesc: String? = null\n\n    private val _playlistsFlow =\n        MutableStateFlow<WebsiteState<Playlists>>(WebsiteState.Loading)\n    val playlistsFlow = _playlistsFlow.asStateFlow()\n\n    fun getPlaylists() {\n        viewModelScope.launch {\n            NetworkRepo.getPlaylists().collect {\n                _playlistsFlow.value = it\n            }\n        }\n    }\n\n    private val _playlistStateFlow =\n        MutableStateFlow<PageLoadingState<MyListItems<HanimeInfo>>>(PageLoadingState.Loading)\n    val playlistStateFlow = _playlistStateFlow.asStateFlow()\n\n    private val _playlistFlow = MutableStateFlow(emptyList<HanimeInfo>())\n    val playlistFlow = _playlistFlow.asStateFlow()\n\n    fun getPlaylistItems(page: Int, listCode: String) {\n        viewModelScope.launch {\n            NetworkRepo.getMyListItems(page, listCode).collect { state ->\n                val prev = _playlistStateFlow.getAndUpdate { state }\n                if (prev is PageLoadingState.Loading) _playlistFlow.value = emptyList()\n                _playlistFlow.update { prevList ->\n                    when (state) {\n                        is PageLoadingState.Success -> prevList + state.info.hanimeInfo\n                        is PageLoadingState.Loading -> emptyList()\n                        else -> prevList\n                    }\n                }\n            }\n        }\n    }\n\n    private val _deleteFromPlaylistFlow = MutableSharedFlow<WebsiteState<Int>>()\n    val deleteFromPlaylistFlow = _deleteFromPlaylistFlow.asSharedFlow()\n\n    fun deleteFromPlaylist(listCode: String, videoCode: String, position: Int) {\n        viewModelScope.launch {\n            NetworkRepo.deleteMyListItems(listCode, videoCode, position, csrfToken).collect {\n                _deleteFromPlaylistFlow.emit(it)\n                _playlistFlow.update { prevList ->\n                    if (it is WebsiteState.Success) {\n                        prevList.toMutableList().apply { removeAt(position) }\n                    } else prevList\n                }\n            }\n        }\n    }\n\n    private val _modifyPlaylistFlow = MutableSharedFlow<WebsiteState<ModifiedPlaylistArgs>>()\n    val modifyPlaylistFlow = _modifyPlaylistFlow.asSharedFlow()\n\n    fun modifyPlaylist(listCode: String, title: String, desc: String, delete: Boolean) {\n        viewModelScope.launch {\n            NetworkRepo.modifyPlaylist(listCode, title, desc, delete, csrfToken).collect {\n                _modifyPlaylistFlow.emit(it)\n                if (delete) {\n                    clearMyListItems()\n                }\n            }\n        }\n    }\n\n    private val _createPlaylistFlow = MutableSharedFlow<WebsiteState<Unit>>()\n    val createPlaylistFlow = _createPlaylistFlow.asSharedFlow()\n\n    fun createPlaylist(title: String, description: String) {\n        viewModelScope.launch {\n            NetworkRepo.createPlaylist(EMPTY_STRING, title, description, csrfToken).collect {\n                _createPlaylistFlow.emit(it)\n            }\n        }\n    }\n\n    fun clearMyListItems() {\n        _playlistStateFlow.value = PageLoadingState.Loading\n    }\n}"
  },
  {
    "path": "app/src/main/java/com/yenaly/han1meviewer/ui/viewmodel/mylist/SubscriptionSubViewModel.kt",
    "content": "package com.yenaly.han1meviewer.ui.viewmodel.mylist\n\nimport android.app.Application\nimport androidx.lifecycle.viewModelScope\nimport com.yenaly.han1meviewer.logic.NetworkRepo\nimport com.yenaly.han1meviewer.logic.model.MyListItems\nimport com.yenaly.han1meviewer.logic.model.MyListType\nimport com.yenaly.han1meviewer.logic.model.Subscription\nimport com.yenaly.han1meviewer.logic.state.PageLoadingState\nimport com.yenaly.han1meviewer.logic.state.WebsiteState\nimport com.yenaly.han1meviewer.ui.viewmodel.AppViewModel.csrfToken\nimport com.yenaly.yenaly_libs.base.YenalyViewModel\nimport kotlinx.coroutines.flow.MutableSharedFlow\nimport kotlinx.coroutines.flow.MutableStateFlow\nimport kotlinx.coroutines.flow.asSharedFlow\nimport kotlinx.coroutines.flow.asStateFlow\nimport kotlinx.coroutines.flow.getAndUpdate\nimport kotlinx.coroutines.flow.update\nimport kotlinx.coroutines.launch\n\nclass SubscriptionSubViewModel(application: Application) : YenalyViewModel(application) {\n\n    var subscriptionPage = 1\n\n    private val _subscriptionStateFlow =\n        MutableStateFlow<PageLoadingState<MyListItems<Subscription>>>(PageLoadingState.Loading)\n    val subscriptionStateFlow = _subscriptionStateFlow.asStateFlow()\n\n    private val _subscriptionFlow =\n        MutableStateFlow(emptyList<Subscription>())\n    val subscriptionFlow = _subscriptionFlow.asStateFlow()\n\n    // 暂时就加载一页，不做分页\n    fun getSubscriptionsWithSinglePage() {\n        viewModelScope.launch {\n            NetworkRepo.getSubscriptions(1).collect { state ->\n                _subscriptionStateFlow.value = state\n                if (state is PageLoadingState.Success) {\n                    _subscriptionFlow.value = state.info.hanimeInfo\n                }\n            }\n        }\n    }\n\n    fun getSubscriptions(page: Int) {\n        viewModelScope.launch {\n            NetworkRepo.getSubscriptions(page).collect { state ->\n                val prev = _subscriptionStateFlow.getAndUpdate { state }\n                if (prev is PageLoadingState.Loading) _subscriptionFlow.value = emptyList()\n                _subscriptionFlow.update { prevList ->\n                    when (state) {\n                        is PageLoadingState.Success -> prevList + state.info.hanimeInfo\n                        is PageLoadingState.Loading -> emptyList()\n                        else -> prevList\n                    }\n                }\n            }\n        }\n    }\n\n    private val _deleteSubscriptionFlow = MutableSharedFlow<WebsiteState<Int>>()\n    val deleteSubscriptionFlow = _deleteSubscriptionFlow.asSharedFlow()\n\n    fun deleteSubscription(artistId: String?, position: Int) {\n        artistId ?: return\n        viewModelScope.launch {\n            NetworkRepo.deleteMyListItems(MyListType.SUBSCRIPTION, artistId, position, csrfToken)\n                .collect {\n                    _deleteSubscriptionFlow.emit(it)\n                    _subscriptionFlow.update { list ->\n                        if (it is WebsiteState.Success) {\n                            list.toMutableList().apply { removeAt(position) }\n                        } else list\n                    }\n                }\n        }\n    }\n}"
  },
  {
    "path": "app/src/main/java/com/yenaly/han1meviewer/ui/viewmodel/mylist/WatchLaterSubViewModel.kt",
    "content": "package com.yenaly.han1meviewer.ui.viewmodel.mylist\n\nimport android.app.Application\nimport androidx.lifecycle.viewModelScope\nimport com.yenaly.han1meviewer.logic.NetworkRepo\nimport com.yenaly.han1meviewer.logic.model.HanimeInfo\nimport com.yenaly.han1meviewer.logic.model.MyListItems\nimport com.yenaly.han1meviewer.logic.model.MyListType\nimport com.yenaly.han1meviewer.logic.state.PageLoadingState\nimport com.yenaly.han1meviewer.logic.state.WebsiteState\nimport com.yenaly.han1meviewer.ui.viewmodel.AppViewModel.csrfToken\nimport com.yenaly.yenaly_libs.base.YenalyViewModel\nimport kotlinx.coroutines.flow.MutableSharedFlow\nimport kotlinx.coroutines.flow.MutableStateFlow\nimport kotlinx.coroutines.flow.asSharedFlow\nimport kotlinx.coroutines.flow.asStateFlow\nimport kotlinx.coroutines.flow.getAndUpdate\nimport kotlinx.coroutines.flow.update\nimport kotlinx.coroutines.launch\n\nclass WatchLaterSubViewModel(application: Application) : YenalyViewModel(application) {\n\n    var watchLaterPage = 1\n\n    private val _watchLaterStateFlow: MutableStateFlow<PageLoadingState<MyListItems<HanimeInfo>>> =\n        MutableStateFlow(PageLoadingState.Loading)\n    val watchLaterStateFlow = _watchLaterStateFlow.asStateFlow()\n\n    private val _watchLaterFlow = MutableStateFlow(emptyList<HanimeInfo>())\n    val watchLaterFlow = _watchLaterFlow.asStateFlow()\n\n    fun getMyWatchLaterItems(page: Int) {\n        viewModelScope.launch {\n            NetworkRepo.getMyListItems(page, MyListType.WATCH_LATER).collect { state ->\n                val prev = _watchLaterStateFlow.getAndUpdate { state }\n                if (prev is PageLoadingState.Loading) _watchLaterFlow.value = emptyList()\n                _watchLaterFlow.update { prevList ->\n                    when (state) {\n                        is PageLoadingState.Success -> prevList + state.info.hanimeInfo\n                        is PageLoadingState.Loading -> emptyList()\n                        else -> prevList\n                    }\n                }\n            }\n        }\n    }\n\n    private val _deleteMyWatchLaterFlow =\n        MutableSharedFlow<WebsiteState<Int>>()\n    val deleteMyWatchLaterFlow = _deleteMyWatchLaterFlow.asSharedFlow()\n\n    fun deleteMyWatchLater(videoCode: String, position: Int) {\n        viewModelScope.launch {\n            NetworkRepo.deleteMyListItems(\n                MyListType.WATCH_LATER, videoCode,\n                position, csrfToken\n            ).collect {\n                _deleteMyWatchLaterFlow.emit(it)\n                _watchLaterFlow.update { list ->\n                    if (it is WebsiteState.Success) {\n                        list.toMutableList().apply { removeAt(position) }\n                    } else list\n                }\n            }\n        }\n    }\n\n    fun clearMyListItems() {\n        _watchLaterStateFlow.value = PageLoadingState.Loading\n    }\n}"
  },
  {
    "path": "app/src/main/java/com/yenaly/han1meviewer/util/Adapters.kt",
    "content": "package com.yenaly.han1meviewer.util\n\nimport android.view.View\nimport android.widget.FrameLayout\nimport android.widget.TextView\nimport androidx.annotation.LayoutRes\nimport com.chad.library.adapter4.BaseDifferAdapter\nimport com.yenaly.han1meviewer.R\nimport com.yenaly.yenaly_libs.utils.applicationContext\nimport kotlin.contracts.ExperimentalContracts\nimport kotlin.contracts.contract\nimport kotlin.coroutines.resume\nimport kotlin.coroutines.suspendCoroutine\n\n/**\n * 设置状态视图\n *\n * 必须在绑定 RV 后才能调用，因为用了 BaseQuickAdapter 里面的 context\n */\nfun com.chad.library.adapter4.BaseQuickAdapter<*, *>.setStateViewLayout(\n    @LayoutRes layoutRes: Int,\n    text: String? = null,\n) {\n    val view = View.inflate(context, layoutRes, FrameLayout(context))\n    view.findViewById<TextView>(R.id.tv_empty).text =\n        text ?: context.getString(R.string.here_is_empty)\n    stateView = view\n}\n\n/**\n * 设置状态视图\n */\nfun com.chad.library.adapter4.BaseQuickAdapter<*, *>.setStateViewLayout(\n    view: View,\n    text: String? = null,\n) {\n    view.findViewById<TextView>(R.id.tv_empty).text =\n        text ?: applicationContext.getString(R.string.here_is_empty)\n    stateView = view\n}\n\nsuspend fun <T : Any> BaseDifferAdapter<T, *>.awaitSubmitList(list: List<T>?) =\n    suspendCoroutine { cont ->\n        submitList(list) {\n            cont.resume(Unit)\n        }\n    }\n\n/**\n * BRVAH4 的 getItem() 现在开始会返回 null 值了，为了避免各种 null 检查，\n * 我们直接在这里加一个非空断言，这样就不用每次都检查了。\n * 而且一般情况下不会为 null\n */\n@OptIn(ExperimentalContracts::class)\n@Suppress(\"NOTHING_TO_INLINE\")\n@Deprecated(\"Use safe call instead, this can easily cause NPE.\", ReplaceWith(\"this ?: return\"))\ninline fun <T> T?.notNull(): T {\n    contract {\n        returns() implies (this@notNull != null)\n    }\n    return checkNotNull(this)\n}"
  },
  {
    "path": "app/src/main/java/com/yenaly/han1meviewer/util/AlertDialogs.kt",
    "content": "package com.yenaly.han1meviewer.util\n\nimport android.content.Context\nimport android.content.DialogInterface\nimport android.graphics.RenderEffect\nimport android.graphics.Shader\nimport android.graphics.drawable.Drawable\nimport android.graphics.drawable.GradientDrawable\nimport android.os.Build\nimport androidx.appcompat.app.AlertDialog\nimport com.google.android.material.color.MaterialColors\nimport com.google.android.material.dialog.MaterialAlertDialogBuilder\nimport com.google.android.material.R\nimport com.yenaly.yenaly_libs.utils.activity\nimport com.yenaly.yenaly_libs.utils.dpF\nimport kotlinx.coroutines.suspendCancellableCoroutine\nimport kotlin.coroutines.resume\n\nfun Context.getDialogDefaultDrawable(): Drawable {\n    val md3BackgroundColor = MaterialColors.getColor(\n        this,\n        R.attr.colorSurfaceContainerHigh,\n        android.graphics.Color.WHITE\n    )\n\n    return GradientDrawable().apply {\n        shape = GradientDrawable.RECTANGLE\n        cornerRadius = 32.dpF\n        setColor(md3BackgroundColor)\n    }\n}\n\n/**\n * 注意：占用了 setOnDismissListener，\n * 使用时不要忘了这一点！\n */\nfun AlertDialog.createDecorBlurEffect(dismissListener: DialogInterface.OnDismissListener? = null) {\n    if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.S) {\n        context.activity?.let { activity ->\n            activity.window.decorView.setRenderEffect(\n                RenderEffect.createBlurEffect(\n                    12.dpF, 12.dpF,\n                    Shader.TileMode.CLAMP\n                )\n            )\n            setOnDismissListener {\n                activity.window.decorView.setRenderEffect(null)\n                dismissListener?.onDismiss(it)\n            }\n        }\n    } else {\n        setOnDismissListener(dismissListener)\n    }\n}\n\ninline fun Context.createAlertDialog(action: MaterialAlertDialogBuilder.() -> Unit): AlertDialog {\n    val ad = MaterialAlertDialogBuilder(this)\n        .setBackground(getDialogDefaultDrawable())\n        .apply(action)\n        .create()\n    return ad\n}\n\n@Suppress(\"NOTHING_TO_INLINE\")\ninline fun AlertDialog.showWithBlurEffect(dismissListener: DialogInterface.OnDismissListener? = null) {\n    // #issue-crashlytics-41e42043fa7396bab68df6aec4578fd2\n    val activity = context.activity\n    if (activity == null || activity.isFinishing) return\n    createDecorBlurEffect(dismissListener)\n    show()\n}\n\ninline fun Context.showAlertDialog(\n    dismissListener: DialogInterface.OnDismissListener? = null,\n    action: MaterialAlertDialogBuilder.() -> Unit\n) {\n    createAlertDialog(action).showWithBlurEffect(dismissListener)\n}\n\n/**\n * Suspends until the user selects a button on the dialog.\n */\nsuspend fun AlertDialog.await(\n    positiveText: String? = null,\n    negativeText: String? = null,\n    neutralText: String? = null,\n    dismissListener: DialogInterface.OnDismissListener? = null,\n) = suspendCancellableCoroutine { cont ->\n    val listener = DialogInterface.OnClickListener { _, which ->\n        when (which) {\n            AlertDialog.BUTTON_POSITIVE -> cont.resume(AlertDialog.BUTTON_POSITIVE)\n            AlertDialog.BUTTON_NEGATIVE -> cont.resume(AlertDialog.BUTTON_NEGATIVE)\n            else -> cont.resume(AlertDialog.BUTTON_NEUTRAL)\n        }\n    }\n\n    if (positiveText != null) setButton(AlertDialog.BUTTON_POSITIVE, positiveText, listener)\n    if (negativeText != null) setButton(AlertDialog.BUTTON_NEGATIVE, negativeText, listener)\n    if (neutralText != null) setButton(AlertDialog.BUTTON_NEUTRAL, neutralText, listener)\n\n    // we can either decide to cancel the coroutine if the dialog\n    // itself gets cancelled, or resume the coroutine with the\n    // value [false]\n    setOnCancelListener { cont.cancel() }\n\n    // if we make this coroutine cancellable, we should also close the\n    // dialog when the coroutine is cancelled\n    cont.invokeOnCancellation { dismiss() }\n\n    // remember to show the dialog before returning from the block,\n    // you won't be able to do it after this function is called!\n    showWithBlurEffect(dismissListener)\n}"
  },
  {
    "path": "app/src/main/java/com/yenaly/han1meviewer/util/Animations.kt",
    "content": "package com.yenaly.han1meviewer.util\n\nimport android.animation.ArgbEvaluator\nimport android.animation.ValueAnimator\nimport androidx.annotation.ColorInt\nimport androidx.fragment.app.Fragment\nimport androidx.lifecycle.DefaultLifecycleObserver\nimport androidx.lifecycle.Lifecycle\nimport androidx.lifecycle.LifecycleOwner\n\n/**\n * Add an update listener to the [ValueAnimator] and remove it when the [LifecycleOwner] is destroyed.\n */\nfun ValueAnimator.addUpdateListener(\n    lifecycle: Lifecycle?,\n    listener: ValueAnimator.AnimatorUpdateListener?,\n) {\n    addUpdateListener(listener)\n    lifecycle?.addObserver(object : DefaultLifecycleObserver {\n        override fun onDestroy(owner: LifecycleOwner) {\n            this@addUpdateListener.removeUpdateListener(listener)\n        }\n    })\n}\n\nfun ValueAnimator.addUpdateListener(\n    fragment: Fragment,\n    listener: ValueAnimator.AnimatorUpdateListener?\n) {\n    if (fragment.isDetached || fragment.view == null) return\n    addUpdateListener(fragment.viewLifecycleOwner.lifecycle, listener)\n}\n\nfun colorTransition(\n    @ColorInt fromColor: Int,\n    @ColorInt toColor: Int,\n    action: (ValueAnimator.() -> Unit)? = null,\n) {\n    val colorAnimation: ValueAnimator = ValueAnimator.ofObject(ArgbEvaluator(), fromColor, toColor)\n    action?.invoke(colorAnimation)\n\n    colorAnimation.start()\n}"
  },
  {
    "path": "app/src/main/java/com/yenaly/han1meviewer/util/AnimeShaders.kt",
    "content": "package com.yenaly.han1meviewer.util\n\nimport android.content.Context\nimport java.io.File\nimport java.io.FileOutputStream\nimport java.io.IOException\n\n\nobject AnimeShaders {\n    const val SHADERS_DIRECTORY = \"shaders\"\n    // 超分辨率滤镜 (质量) A\n    val mpvSuperResolutionArray = arrayOf(\n        \"Anime4K_Clamp_Highlights.glsl\",\n        \"Anime4K_Restore_CNN_VL.glsl\",\n        \"Anime4K_Upscale_CNN_x2_VL.glsl\",\n        \"Anime4K_AutoDownscalePre_x2.glsl\",\n        \"Anime4K_AutoDownscalePre_x4.glsl\",\n        \"Anime4K_Upscale_CNN_x2_M.glsl\"\n    )\n    // 超分辨率滤镜 (效率) A+A\n    val mpvSuperResolutionLiteArray = arrayOf(\n        \"Anime4K_Clamp_Highlights.glsl\",\n        \"Anime4K_Restore_CNN_M.glsl\",\n        \"Anime4K_Restore_CNN_S.glsl\",\n        \"Anime4K_Upscale_CNN_x2_M.glsl\",\n        \"Anime4K_AutoDownscalePre_x2.glsl\",\n        \"Anime4K_AutoDownscalePre_x4.glsl\",\n        \"Anime4K_Upscale_CNN_x2_S.glsl\"\n    )\n\n    /**\n     * 从 assets/shaders/ 复制所有文件到应用私有目录的 shaders/ 文件夹\n     * @return 成功复制的文件数量，-1 表示出错\n     */\n    fun copyShaderAssets(context: Context): Int {\n        try {\n            val targetDir = File(context.filesDir, SHADERS_DIRECTORY)\n            if (!targetDir.exists()) {\n                targetDir.mkdirs()\n            }\n\n            val assetManager = context.assets\n            val assetFiles = assetManager.list(SHADERS_DIRECTORY) ?: return 0\n\n            var copiedCount = 0\n            for (filename in assetFiles) {\n                val targetFile = File(targetDir, filename)\n                assetManager.open(\"$SHADERS_DIRECTORY/$filename\").use { inputStream ->\n                    FileOutputStream(targetFile).use { outputStream ->\n                        inputStream.copyTo(outputStream)\n                        copiedCount++\n                    }\n                }\n            }\n            return copiedCount\n        } catch (e: IOException) {\n            e.printStackTrace()\n            return -1\n        }\n    }\n\n    /**\n     * 获取应用私有目录的 shaders/ 文件夹路径\n     */\n    fun getShader(context: Context, type: Int): String {\n        val shadersDir = File(context.filesDir, SHADERS_DIRECTORY)\n        if (!shadersDir.exists()) {\n            throw IllegalStateException(\"Shader 文件夹不存在: $shadersDir\")\n        }\n\n        val shaderFiles = when (type) {\n            // 效率\n            1 -> mpvSuperResolutionLiteArray\n            // 质量\n            2 -> mpvSuperResolutionArray\n            else -> throw IllegalArgumentException(\"未知 Shader 类型: $type\")\n        }\n        return shaderFiles.joinToString(separator = \":\") { shaderFile ->\n            File(shadersDir, shaderFile).absolutePath\n        }\n    }\n\n}"
  },
  {
    "path": "app/src/main/java/com/yenaly/han1meviewer/util/Colors.kt",
    "content": "package com.yenaly.han1meviewer.util\n\nimport android.content.res.ColorStateList\nimport androidx.annotation.ColorInt\n\nfun @receiver:ColorInt Int.toColorStateList() = ColorStateList.valueOf(this)"
  },
  {
    "path": "app/src/main/java/com/yenaly/han1meviewer/util/Cookies.kt",
    "content": "package com.yenaly.han1meviewer.util\n\nimport android.util.Log\nimport com.yenaly.han1meviewer.Preferences\nimport okhttp3.Cookie\n\n@JvmInline\nvalue class CookieString(val cookie: String)\n\n/**\n * 主要用於 [HCookieJar][com.yenaly.han1meviewer.logic.network.HCookieJar]，最好不要用到其他地方。\n */\nfun CookieString.toLoginCookieList(domain: String): List<Cookie> {\n    val cookieList = mutableListOf<Cookie>().also {\n        it += preferencesCookieList(domain)\n    }\n    cookie.split(';').forEach { cookie ->\n        if (cookie.isNotBlank()) {\n            val name = cookie.substringBefore('=').trim()\n            val value = cookie.substringAfter('=').trim()\n            cookieList += Cookie.Builder().domain(domain).name(name).value(value).build()\n        }\n    }\n    return cookieList.also {\n        Log.d(\"CookieString\", \"toCookieList: $it\")\n    }\n}\n\n/**\n * 每次退出登入後都會清除cookie，但是這樣可能會清除掉很多保存在cookie中的偏好，比如影片語言之類。\n *\n * 讓[preferencesCookieList]成爲 存在偏好設置 但不存在個人信息 的[emptyList]\n */\nprivate fun preferencesCookieList(domain: String): List<Cookie> {\n    val videoLanguage = Preferences.videoLanguage\n    val videoLanguageCookie = Cookie.Builder().domain(domain)\n        .name(\"user_lang\")\n        .value(videoLanguage)\n        .build()\n    return listOf(videoLanguageCookie)\n}"
  },
  {
    "path": "app/src/main/java/com/yenaly/han1meviewer/util/Extend.kt",
    "content": "package com.yenaly.han1meviewer.util\n\nimport android.content.res.Resources\n\nfun Int.dpToPx(): Int = (this * Resources.getSystem().displayMetrics.density).toInt()\nfun Float.dpToPx(): Float = (this * Resources.getSystem().displayMetrics.density)\n"
  },
  {
    "path": "app/src/main/java/com/yenaly/han1meviewer/util/Files.kt",
    "content": "package com.yenaly.han1meviewer.util\n\nimport android.content.ActivityNotFoundException\nimport android.content.Context\nimport android.content.Intent\nimport android.os.Environment\nimport androidx.core.content.FileProvider\nimport androidx.core.net.toFile\nimport androidx.core.net.toUri\nimport com.yenaly.han1meviewer.FILE_PROVIDER_AUTHORITY\nimport com.yenaly.han1meviewer.FROM_DOWNLOAD\nimport com.yenaly.han1meviewer.HJson\nimport com.yenaly.han1meviewer.R\nimport com.yenaly.han1meviewer.VIDEO_CODE\nimport com.yenaly.han1meviewer.ui.activity.VideoActivity\nimport com.yenaly.yenaly_libs.utils.applicationContext\nimport com.yenaly.yenaly_libs.utils.showShortToast\nimport kotlinx.coroutines.Dispatchers\nimport kotlinx.coroutines.ensureActive\nimport kotlinx.coroutines.withContext\nimport kotlinx.serialization.ExperimentalSerializationApi\nimport kotlinx.serialization.json.decodeFromStream\nimport java.io.File\nimport java.io.InputStream\nimport java.io.OutputStream\n\n@Deprecated(\n    \"Use alternative\",\n    ReplaceWith(\n        \"HFileManager.appDownloadFolder\",\n        imports = [\"com.yenaly.han1meviewer.HFileManager\"]\n    )\n)\nval hanimeVideoLocalFolder get() = applicationContext.getExternalFilesDir(Environment.DIRECTORY_MOVIES)\n\n@Deprecated(\n    \"Use alternative\",\n    ReplaceWith(\n        \"HFileManager.DEF_VIDEO_TYPE\",\n        imports = [\"com.yenaly.han1meviewer.HFileManager\"]\n    )\n)\nconst val DEF_VIDEO_TYPE = \"mp4\"\n\n@Deprecated(\n    \"Use alternative\",\n    ReplaceWith(\n        \"HFileManager.createVideoName(title, quality, suffix)\",\n        imports = [\"com.yenaly.han1meviewer.HFileManager\"]\n    )\n)\nfun createDownloadName(title: String, quality: String, suffix: String = DEF_VIDEO_TYPE) =\n    \"${title}_${quality}.${suffix}\"\n\n@Deprecated(\n    \"Use alternative\",\n    ReplaceWith(\n        \"HFileManager.getDownloadVideoFile(videoCode, title, quality, suffix)\",\n        imports = [\"com.yenaly.han1meviewer.HFileManager\"]\n    )\n)\nfun getDownloadedHanimeFile(title: String, quality: String, suffix: String = DEF_VIDEO_TYPE): File {\n    return File(hanimeVideoLocalFolder, createDownloadName(title, quality, suffix))\n}\n\n@Deprecated(\"不用了\")\nfun checkDownloadedHanimeFile(startsWith: String): Boolean {\n    return hanimeVideoLocalFolder?.let { folder ->\n        folder.listFiles()?.any { it.name.startsWith(startsWith) }\n    } == true\n}\n\n/**\n * Must be Activity Context!\n */\nfun Context.openDownloadedHanimeVideoLocally(\n    uri: String, onFileNotFound: (() -> Unit)? = null,\n) {\n    val videoFile = uri.toUri().toFile()\n    if (!videoFile.exists()) {\n        onFileNotFound?.invoke()\n        return\n    }\n    val fileUri = FileProvider.getUriForFile(\n        this, FILE_PROVIDER_AUTHORITY, videoFile\n    )\n    val intent = Intent(Intent.ACTION_VIEW)\n    intent.setDataAndType(fileUri, \"video/*\")\n    intent.addFlags(Intent.FLAG_GRANT_READ_URI_PERMISSION)\n    try {\n        startActivity(intent)\n    } catch (e: ActivityNotFoundException) {\n        showShortToast(R.string.action_not_support)\n        e.printStackTrace()\n    }\n}\n\nfun Context.openDownloadedHanimeVideoInActivity(videoCode: String) {\n    val intent = Intent(this, VideoActivity::class.java)\n    intent.putExtra(FROM_DOWNLOAD, true)\n    intent.putExtra(VIDEO_CODE, videoCode)\n    startActivity(intent)\n}\n\n/**\n * copyTo with progress\n */\nsuspend fun InputStream.copyTo(\n    out: OutputStream,\n    contentLength: Long,\n    startOffset: Long = 0,\n    bufferSize: Int = DEFAULT_BUFFER_SIZE,\n    progress: (suspend (Int) -> Unit)? = null,\n    downloadLength: (suspend (Long) -> Unit)? = null,\n): Long {\n    return withContext(Dispatchers.IO) {\n        this@copyTo.use {\n            var bytesCopied: Long = startOffset\n            val buffer = ByteArray(bufferSize)\n            var bytes = read(buffer)\n            var percent = 0\n            while (bytes >= 0) {\n                ensureActive()\n                out.write(buffer, 0, bytes)\n                bytesCopied += bytes\n                downloadLength?.invoke(bytesCopied)\n                val newPercent = (bytesCopied * 100 / contentLength).toInt()\n                if (newPercent != percent) {\n                    percent = newPercent\n                    progress?.invoke(percent.coerceAtMost(100))\n                }\n                bytes = read(buffer)\n            }\n            bytesCopied\n        }\n    }\n}\n\n@OptIn(ExperimentalSerializationApi::class)\ninline fun <reified T> loadAssetAs(filePath: String): T? = runCatching {\n    applicationContext.assets.open(filePath).use { inputStream ->\n        HJson.decodeFromStream<T>(inputStream)\n    }\n}.getOrNull()"
  },
  {
    "path": "app/src/main/java/com/yenaly/han1meviewer/util/Firebases.kt",
    "content": "package com.yenaly.han1meviewer.util\n\nimport android.app.Activity\nimport android.util.Log\nimport androidx.fragment.app.Fragment\nimport com.google.firebase.Firebase\nimport com.google.firebase.analytics.FirebaseAnalytics\nimport com.google.firebase.analytics.analytics\nimport com.google.firebase.analytics.logEvent\n\nfun Activity.logScreenViewEvent(fragment: Fragment) {\n    Firebase.analytics.logEvent(FirebaseAnalytics.Event.SCREEN_VIEW) {\n        // example: MainActivity-HomePageFragment\n        val screenName = this@logScreenViewEvent.javaClass.simpleName +\n                \"-\" + fragment.javaClass.simpleName\n        Log.d(\"logScreenViewEvent\", \"screenName: $screenName\")\n        param(FirebaseAnalytics.Param.SCREEN_NAME, screenName)\n        param(FirebaseAnalytics.Param.SCREEN_CLASS, fragment.javaClass.simpleName)\n    }\n}"
  },
  {
    "path": "app/src/main/java/com/yenaly/han1meviewer/util/GridSpacingDecoration.kt",
    "content": "package com.yenaly.han1meviewer.util\n\nimport android.graphics.Rect\nimport android.util.Log\nimport android.view.View\nimport androidx.recyclerview.widget.RecyclerView\n\n\nclass GridSpacingDecoration(\n    private val spanCount: Int,\n    private val spacing: Int,\n    private val start: Int,\n    private val includeEdge: Boolean\n) : RecyclerView.ItemDecoration() {\n\n    override fun getItemOffsets(\n        outRect: Rect,\n        view: View,\n        parent: RecyclerView,\n        state: RecyclerView.State\n    ) {\n        val position = parent.getChildAdapterPosition(view) - start\n        if (position < 0) return\n        val column = (position) % spanCount\n        Log.i(\"TAG\", \"getItemOffsets: $column ${spacing - column * spacing / spanCount} ${(column + 1) * spacing / spanCount}\")\n\n        if (includeEdge) {\n            outRect.left = spacing - column * spacing / spanCount\n            outRect.right = (column + 1) * spacing / spanCount\n        } else {\n            outRect.left = column * spacing / spanCount\n            outRect.right = spacing - (column + 1) * spacing / spanCount\n        }\n    }\n}\n"
  },
  {
    "path": "app/src/main/java/com/yenaly/han1meviewer/util/HImageMeower.kt",
    "content": "package com.yenaly.han1meviewer.util\n\nimport android.util.Log\nimport android.widget.ImageView\nimport coil.ImageLoader\nimport coil.imageLoader\nimport coil.request.ErrorResult\nimport coil.request.ImageRequest\nimport coil.request.ImageResult\nimport com.yenaly.yenaly_libs.utils.applicationContext\nimport okhttp3.OkHttpClient\nimport java.lang.ref.WeakReference\nimport java.util.concurrent.TimeUnit\n\n@Suppress(\"NOTHING_TO_INLINE\")\nobject HImageMeower {\n\n    private const val TAG = \"CoilImageNyanner\"\n\n    private val okHttpClient = OkHttpClient.Builder()\n        .connectTimeout(5, TimeUnit.SECONDS)\n        .build()\n\n    private val imageLoader = ImageLoader.Builder(applicationContext)\n        .okHttpClient(okHttpClient)\n        .build()\n\n    suspend fun execute(data: Any): ImageResult {\n        Log.d(TAG, \"execute: $data\")\n        return imageLoader.execute(\n            ImageRequest.Builder(applicationContext).data(data).build()\n        )\n    }\n\n    inline fun placeholder(height: Int, width: Int, blur: Int = 8) =\n        \"https://picsum.photos/$width/$height/?blur=$blur\"\n\n    fun ImageView.loadUnhappily(data: Any?, fallbackData: Any?) {\n        Log.d(TAG, \"primary: $data, fallback: $fallbackData\")\n        val primaryRequest = ImageRequest.Builder(context)\n            .data(data ?: fallbackData)\n            .crossfade(true)\n            .target(this)\n            .listener(object : ImageRequest.Listener {\n                private val ivRef = WeakReference(this@loadUnhappily)\n                override fun onError(request: ImageRequest, result: ErrorResult) {\n                    fallbackData?.let { ivRef.get()?.loadUnhappily(it, null) }\n                }\n            }).build()\n        context.imageLoader.enqueue(primaryRequest)\n    }\n}"
  },
  {
    "path": "app/src/main/java/com/yenaly/han1meviewer/util/MediaUtils.kt",
    "content": "package com.yenaly.han1meviewer.util\n\nimport android.content.Context\nimport android.content.Intent\nimport android.util.Log\nimport androidx.core.content.FileProvider\nimport androidx.core.net.toFile\nimport androidx.core.net.toUri\nimport com.yenaly.yenaly_libs.utils.showShortToast\n\nobject MediaUtils {\n\n    fun playMedia(context: Context, uri: String) {\n        val intent = Intent(Intent.ACTION_VIEW).apply {\n            val videoFile = uri.toUri().toFile()\n            val uri = FileProvider.getUriForFile(\n                context,\n                \"${context.packageName}.fileProvider\",\n                videoFile\n            )\n            setDataAndType(uri, \"video/*\")\n            addFlags(Intent.FLAG_GRANT_READ_URI_PERMISSION)\n        }\n\n\n        if (intent.resolveActivity(context.packageManager) != null) {\n            context.startActivity(intent)\n        } else {\n            showShortToast(\"未找到可用的播放器\")\n        }\n    }\n}"
  },
  {
    "path": "app/src/main/java/com/yenaly/han1meviewer/util/Networks.kt",
    "content": "package com.yenaly.han1meviewer.util\n\nimport com.google.common.util.concurrent.ListenableFuture\nimport kotlinx.coroutines.CancellationException\nimport kotlinx.coroutines.suspendCancellableCoroutine\nimport okhttp3.Call\nimport okhttp3.Callback\nimport okhttp3.Response\nimport java.io.IOException\nimport java.util.concurrent.ExecutionException\nimport java.util.concurrent.Executor\nimport kotlin.coroutines.resume\nimport kotlin.coroutines.resumeWithException\n\nsuspend fun <R> ListenableFuture<R>.await(): R {\n    // Fast path\n    if (isDone) {\n        try {\n            return get()\n        } catch (e: ExecutionException) {\n            throw e.cause ?: e\n        }\n    }\n    return suspendCancellableCoroutine { cancellableContinuation ->\n        addListener(\n            {\n                try {\n                    cancellableContinuation.resume(get())\n                } catch (throwable: Throwable) {\n                    val cause = throwable.cause ?: throwable\n                    when (throwable) {\n                        is java.util.concurrent.CancellationException ->\n                            cancellableContinuation.cancel(cause)\n\n                        else -> cancellableContinuation.resumeWithException(cause)\n                    }\n                }\n            },\n            DirectExecutor\n        )\n\n        cancellableContinuation.invokeOnCancellation {\n            cancel(false)\n        }\n    }\n}\n\n/**\n * Suspend extension that allows suspend [Call] inside coroutine.\n */\nsuspend fun Call.await(): Response {\n    return suspendCancellableCoroutine { continuation ->\n        enqueue(object : Callback {\n            override fun onFailure(call: Call, e: IOException) {\n                if (continuation.isCancelled) return\n                continuation.resumeWithException(e)\n            }\n\n            override fun onResponse(call: Call, response: Response) {\n                continuation.resume(response)\n            }\n        })\n        continuation.invokeOnCancellation { cancel() }\n    }\n}\n\n/**\n * Run suspend catching\n *\n * @param block suspend block\n */\ninline fun <R> runSuspendCatching(block: () -> R): Result<R> {\n    return try {\n        Result.success(block())\n    } catch (c: CancellationException) {\n        throw c\n    } catch (e: Throwable) {\n        Result.failure(e)\n    }\n}\n\nprivate data object DirectExecutor : Executor {\n\n    override fun execute(command: Runnable) {\n        command.run()\n    }\n}\n"
  },
  {
    "path": "app/src/main/java/com/yenaly/han1meviewer/util/Parcels.kt",
    "content": "package com.yenaly.han1meviewer.util\n\nimport android.os.Parcel\n\n@Suppress(\"NOTHING_TO_INLINE\")\ninline fun Parcel.readBooleanCompat(): Boolean {\n    return readInt() != 0\n}\n\n@Suppress(\"NOTHING_TO_INLINE\")\ninline fun Parcel.writeBooleanCompat(value: Boolean) {\n    writeInt(if (value) 1 else 0)\n}"
  },
  {
    "path": "app/src/main/java/com/yenaly/han1meviewer/util/Permissions.kt",
    "content": "package com.yenaly.han1meviewer.util\n\nimport android.Manifest\nimport android.content.Context\nimport android.content.Intent\nimport android.net.Uri\nimport android.os.Build\nimport android.os.Environment\nimport android.provider.Settings\nimport androidx.activity.result.PickVisualMediaRequest\nimport androidx.activity.result.contract.ActivityResultContracts\nimport androidx.appcompat.app.AlertDialog\nimport androidx.core.app.ActivityCompat.startActivityForResult\nimport com.yenaly.han1meviewer.R\nimport com.yenaly.yenaly_libs.utils.awaitActivityResult\nimport com.yenaly.yenaly_libs.utils.requestPermission\nimport com.yenaly.yenaly_libs.utils.requireComponentActivity\nimport com.yenaly.yenaly_libs.utils.showShortToast\nimport androidx.core.net.toUri\n\n\n/**\n * 请求选择图片或视频\n */\nsuspend fun Context.pickVisualMedia(type: ActivityResultContracts.PickVisualMedia.VisualMediaType): Uri? =\n    awaitActivityResult(\n        ActivityResultContracts.PickVisualMedia(),\n        PickVisualMediaRequest.Builder().setMediaType(type).build()\n    )\n\n/**\n * 獲得發送通知權限\n */\nsuspend fun Context.requestPostNotificationPermission(): Boolean {\n    if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.TIRAMISU) {\n        val granted = requestPermission(Manifest.permission.POST_NOTIFICATIONS)\n        if (!granted) {\n            val res = showPostNotificationPermissionDialog()\n            if (res == AlertDialog.BUTTON_NEGATIVE) {\n                showShortToast(R.string.msg_deny_download_notification)\n                return false\n            }\n            requestPermission(Manifest.permission.POST_NOTIFICATIONS)\n        }\n    }\n    return true\n}\n\n/**\n * 顯示發送通知權限對話框\n */\nprivate suspend fun Context.showPostNotificationPermissionDialog(): Int {\n    val dialog = requireComponentActivity().createAlertDialog {\n        setTitle(R.string.allow_post_notification)\n        setMessage(R.string.reason_for_download_notification)\n    }\n    return dialog.await(getString(R.string.allow), getString(R.string.deny))\n}\n\n/**\n * 请求安装权限\n */\nsuspend fun Context.requestInstallPermission(): Boolean {\n    if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) {\n        if (packageManager.canRequestPackageInstalls()) return true\n        val granted = requestPermission(Manifest.permission.REQUEST_INSTALL_PACKAGES)\n        if (!granted) {\n            val res = showInstallPermissionDialog()\n            if (res == AlertDialog.BUTTON_NEGATIVE) return false\n            awaitActivityResult(\n                ActivityResultContracts.StartActivityForResult(),\n                Intent(\n                    Settings.ACTION_MANAGE_UNKNOWN_APP_SOURCES,\n                    \"package:$packageName\".toUri(),\n                ),\n            )\n            requestPermission(Manifest.permission.REQUEST_INSTALL_PACKAGES)\n        }\n        return packageManager.canRequestPackageInstalls()\n    }\n    return true\n}\n\n/**\n * 显示安装权限对话框\n */\nprivate suspend fun Context.showInstallPermissionDialog(): Int {\n    val dialog = requireComponentActivity().createAlertDialog {\n        setTitle(R.string.allow_install_from_unknown_app_sources)\n        setMessage(R.string.reason_for_allow_install_from_unknown_app_sources)\n    }\n    return dialog.await(getString(R.string.go_to_settings), getString(R.string.deny))\n}\n\n\n/**\n * 显示存储权限对话框\n */\nprivate suspend fun Context.showExternalStoragePermissionDialog(): Int {\n    val dialog = requireComponentActivity().createAlertDialog {\n        setTitle(R.string.allow_external_storage)\n        setMessage(R.string.reason_for_allow_external_storage)\n    }\n    return dialog.await(getString(R.string.go_to_settings), getString(R.string.deny))\n}\n\n/**\n * 请求存储权限\n */\nsuspend fun Context.requestExternalStoragePermission(): Boolean {\n    if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.R) {\n        if (Environment.isExternalStorageManager()) return true\n        val granted = requestPermission(Manifest.permission.MANAGE_EXTERNAL_STORAGE)\n        if (!granted) {\n            val res = showExternalStoragePermissionDialog()\n            if (res == AlertDialog.BUTTON_NEGATIVE) return false\n            awaitActivityResult(\n                ActivityResultContracts.StartActivityForResult(),\n                Intent(\n                    Settings.ACTION_MANAGE_APP_ALL_FILES_ACCESS_PERMISSION,\n                    \"package:$packageName\".toUri(),\n                ),\n            )\n            requestPermission(Manifest.permission.MANAGE_EXTERNAL_STORAGE)\n        }\n        return Environment.isExternalStorageManager()\n    }\n    return true\n}"
  },
  {
    "path": "app/src/main/java/com/yenaly/han1meviewer/util/Platform.kt",
    "content": "package com.yenaly.han1meviewer.util\n\nimport android.os.Build\n\nobject Platform {\n    val isHuaweiDevice = Build.MANUFACTURER.equals(\"HUAWEI\", ignoreCase = true)\n}"
  },
  {
    "path": "app/src/main/java/com/yenaly/han1meviewer/util/Preferences.kt",
    "content": "package com.yenaly.han1meviewer.util\n\nimport androidx.preference.SeekBarPreference\n\nfun SeekBarPreference.setSummaryConverter(\n    defValue: Int,\n    converter: (Int) -> CharSequence?,\n    action: ((Int) -> Unit)? = null\n) {\n    setDefaultValue(defValue)\n    summary = converter(value)\n    setOnPreferenceChangeListener { _, newValue ->\n        summary = converter(newValue as Int)\n        action?.invoke(newValue)\n        true\n    }\n}"
  },
  {
    "path": "app/src/main/java/com/yenaly/han1meviewer/util/TextViews.kt",
    "content": "package com.yenaly.han1meviewer.util\n\nimport android.content.Context\nimport android.text.Layout\nimport android.text.Spannable\nimport android.text.SpannableStringBuilder\nimport android.text.Spanned\nimport android.text.StaticLayout\nimport android.text.TextPaint\nimport android.text.style.ClickableSpan\nimport android.view.View\nimport android.widget.TextView\nimport androidx.core.text.HtmlCompat\nimport androidx.core.text.method.LinkMovementMethodCompat\nimport com.yenaly.han1meviewer.R\nimport com.yenaly.yenaly_libs.utils.getThemeColor\nimport kotlin.math.roundToInt\n\nprivate abstract class NoUnderlineClickSpan(val context: Context) : ClickableSpan() {\n    override fun updateDrawState(ds: TextPaint) {\n        ds.isUnderlineText = false\n        ds.color = context.getThemeColor(android.R.attr.colorPrimary)\n    }\n}\n\nfun TextView.setResizableText(\n    fullText: String,\n    maxLines: Int,\n    viewMore: Boolean,\n    applyExtraHighlights: ((Spannable) -> (Spannable))? = null,\n) {\n    val width = width\n    if (width <= 0) {\n        post {\n            setResizableText(fullText, maxLines, viewMore, applyExtraHighlights)\n        }\n        return\n    }\n    movementMethod = LinkMovementMethodCompat.getInstance()\n    // Since we take the string character by character, we don't want to break up the Windows-style\n    // line endings.\n    val adjustedText = fullText.replace(\"\\r\\n\", \"\\n\")\n    // Check if even the text has to be resizable.\n    val textLayout = StaticLayout.Builder.obtain(\n        adjustedText, 0, adjustedText.length, paint,\n        width - paddingLeft - paddingRight\n    ).setLineSpacing(lineSpacingExtra, lineSpacingMultiplier)\n        .setAlignment(Layout.Alignment.ALIGN_NORMAL)\n        .setIncludePad(includeFontPadding)\n        .build()\n    if (textLayout.lineCount <= maxLines || adjustedText.isEmpty()) {\n        // No need to add 'read more' / 'read less' since the text fits just as well (less than max lines #).\n        val htmlText = adjustedText.replace(\"\\n\", \"<br/>\")\n        text = addClickablePartTextResizable(\n            fullText,\n            maxLines,\n            HtmlCompat.fromHtml(htmlText, HtmlCompat.FROM_HTML_MODE_COMPACT),\n            null,\n            viewMore,\n            applyExtraHighlights\n        )\n        return\n    }\n    val charactersAtLineEnd = textLayout.getLineEnd(maxLines - 1)\n    val suffixText =\n        if (viewMore) context.getString(R.string.expand) else context.getString(R.string.collapse)\n    var charactersToTake = charactersAtLineEnd - suffixText.length / 2 // Good enough first guess\n    if (charactersToTake <= 0) {\n        // Happens when text is empty\n        val htmlText = adjustedText.replace(\"\\n\", \"<br/>\")\n        text = addClickablePartTextResizable(\n            fullText,\n            maxLines,\n            HtmlCompat.fromHtml(htmlText, HtmlCompat.FROM_HTML_MODE_COMPACT),\n            null,\n            viewMore,\n            applyExtraHighlights\n        )\n        return\n    }\n    if (!viewMore) {\n        // We can set the text immediately because nothing needs to be measured\n        val htmlText = adjustedText.replace(\"\\n\", \"<br/>\")\n        text = addClickablePartTextResizable(\n            fullText,\n            maxLines,\n            HtmlCompat.fromHtml(htmlText, HtmlCompat.FROM_HTML_MODE_COMPACT),\n            suffixText,\n            false,\n            applyExtraHighlights\n        )\n        return\n    }\n    val lastHasNewLine =\n        adjustedText.substring(\n            textLayout.getLineStart(maxLines - 1),\n            textLayout.getLineEnd(maxLines - 1)\n        )\n            .contains(\"\\n\")\n    val linedText = if (lastHasNewLine) {\n        val charactersPerLine =\n            textLayout.getLineEnd(0) / (textLayout.getLineWidth(0) / textLayout.ellipsizedWidth.toFloat())\n        // non breaking space, will not be thrown away by HTML parser\n        val lineOfSpaces = \"\\u00A0\".repeat(charactersPerLine.roundToInt())\n        charactersToTake += lineOfSpaces.length - 1\n        adjustedText.take(textLayout.getLineStart(maxLines - 1)) +\n                adjustedText.substring(\n                    textLayout.getLineStart(maxLines - 1),\n                    textLayout.getLineEnd(maxLines - 1)\n                )\n                    .replace(\"\\n\", lineOfSpaces) +\n                adjustedText.substring(textLayout.getLineEnd(maxLines - 1))\n    } else {\n        adjustedText\n    }\n    // Check if we perhaps need to even add characters? Happens very rarely, but can be possible if there was a long word just wrapped\n    val shortenedString = linedText.take(charactersToTake)\n    val shortenedStringWithSuffix = shortenedString + suffixText\n    val shortenedStringWithSuffixLayout = StaticLayout.Builder.obtain(\n        shortenedStringWithSuffix, 0, shortenedStringWithSuffix.length, paint,\n        width - paddingLeft - paddingRight\n    ).setLineSpacing(lineSpacingExtra, lineSpacingMultiplier)\n        .setAlignment(Layout.Alignment.ALIGN_NORMAL)\n        .setIncludePad(includeFontPadding)\n        .build()\n    val modifier: Int\n    if (shortenedStringWithSuffixLayout.getLineEnd(maxLines - 1) >= shortenedStringWithSuffix.length) {\n        modifier = 1\n        charactersToTake-- // We might just be at the right position already\n    } else {\n        modifier = -1\n    }\n    do {\n        charactersToTake += modifier\n        val baseString = linedText.take(charactersToTake)\n        val appended = baseString + suffixText\n        val newLayout = StaticLayout.Builder.obtain(\n            appended, 0, appended.length, paint,\n            width - paddingLeft - paddingRight\n        ).setLineSpacing(lineSpacingExtra, lineSpacingMultiplier)\n            .setAlignment(Layout.Alignment.ALIGN_NORMAL)\n            .setIncludePad(includeFontPadding)\n            .build()\n    } while ((modifier < 0 && newLayout.getLineEnd(maxLines - 1) < appended.length) ||\n        (modifier > 0 && newLayout.getLineEnd(maxLines - 1) >= appended.length)\n    )\n    if (modifier > 0) {\n        charactersToTake-- // We went overboard with 1 char, fixing that\n    }\n    // We need to convert newlines because we are going over to HTML now\n    val htmlText = linedText.take(charactersToTake).replace(\"\\n\", \"<br/>\")\n    text = addClickablePartTextResizable(\n        fullText,\n        maxLines,\n        HtmlCompat.fromHtml(htmlText, HtmlCompat.FROM_HTML_MODE_COMPACT),\n        suffixText,\n        true,\n        applyExtraHighlights\n    )\n}\n\nprivate fun TextView.addClickablePartTextResizable(\n    fullText: String,\n    maxLines: Int,\n    shortenedText: Spanned,\n    clickableText: String?,\n    viewMore: Boolean,\n    applyExtraHighlights: ((Spannable) -> (Spannable))? = null,\n): Spannable {\n    val builder = SpannableStringBuilder(shortenedText)\n    if (clickableText != null) {\n        builder.append(clickableText)\n        // val startIndexOffset = if (viewMore) 4 else 0 // Do not highlight the 3 dots and the space\n        val startIndexOffset = 0\n        builder.setSpan(\n            object : NoUnderlineClickSpan(context) {\n                override fun onClick(widget: View) {\n                    if (viewMore) {\n                        setResizableText(fullText, maxLines, false, applyExtraHighlights)\n                    } else {\n                        setResizableText(fullText, maxLines, true, applyExtraHighlights)\n                    }\n                }\n            },\n            builder.indexOf(clickableText) + startIndexOffset,\n            builder.indexOf(clickableText) + clickableText.length,\n            0\n        )\n    }\n    if (applyExtraHighlights != null) {\n        return applyExtraHighlights(builder)\n    }\n    return builder\n}"
  },
  {
    "path": "app/src/main/java/com/yenaly/han1meviewer/util/Versions.kt",
    "content": "package com.yenaly.han1meviewer.util\n\nimport android.content.Context\nimport android.content.Intent\nimport android.graphics.Typeface\nimport androidx.appcompat.app.AlertDialog\nimport androidx.core.content.FileProvider\nimport com.itxca.spannablex.spannable\nimport com.yenaly.han1meviewer.BuildConfig\nimport com.yenaly.han1meviewer.FILE_PROVIDER_AUTHORITY\nimport com.yenaly.han1meviewer.Preferences\nimport com.yenaly.han1meviewer.R\nimport com.yenaly.han1meviewer.logic.model.github.Latest\nimport com.yenaly.han1meviewer.worker.HUpdateWorker\nimport com.yenaly.yenaly_libs.utils.applicationContext\nimport com.yenaly.yenaly_libs.utils.showShortToast\nimport java.io.File\n\nval Context.updateFile: File get() = File(applicationContext.cacheDir, \"update.apk\")\n\nval UPDATE_ZIP_PATH: File get() = File(applicationContext.cacheDir, \"update.zip\")\n\nfun checkNeedUpdate(versionName: String): Boolean {\n    val latestVersionCode = versionName.substringAfter(\"+\", \"\").toIntOrNull() ?: Int.MAX_VALUE\n    return BuildConfig.VERSION_CODE < latestVersionCode\n}\n\nsuspend fun Context.showUpdateDialog(latest: Latest) {\n    val spannable = spannable {\n        getString(R.string.new_version_found).span {\n            style(Typeface.BOLD)\n            relativeSize(1.2f)\n        }\n        newline()\n        latest.version.text()\n        newline()\n        getString(R.string.update_content).span {\n            style(Typeface.BOLD)\n            relativeSize(1.2f)\n        }\n        newline()\n        latest.changelog.text()\n    }\n    val dialog = createAlertDialog {\n        setTitle(R.string.new_version_found)\n        setMessage(spannable)\n        setCancelable(false)\n    }\n    val res = dialog.await(\n        positiveText = getString(R.string.update),\n        negativeText = getString(R.string.cancel),\n    )\n    if (res == AlertDialog.BUTTON_POSITIVE) {\n        val update = this.getUpdateIfExists(latest)\n        if (update != null) {\n            installApkPackage(update)\n        } else {\n            requestPostNotificationPermission()\n            HUpdateWorker.enqueue(this.applicationContext, latest)\n            showShortToast(R.string.update_download_background)\n        }\n    }\n    dialog.dismiss()\n}\n\nprivate fun Context.getUpdateIfExists(latest: Latest): File? {\n    val nodeId = Preferences.updateNodeId\n    return updateFile.takeIf { file ->\n        !BuildConfig.DEBUG && file.exists() && nodeId.isNotEmpty() && nodeId == latest.nodeId\n    }\n}\n\nsuspend fun Context.installApkPackage(file: File) {\n    val canInstall = requestInstallPermission()\n    if (canInstall) {\n        val uri = FileProvider.getUriForFile(this.applicationContext, FILE_PROVIDER_AUTHORITY, file)\n        val intent = Intent(Intent.ACTION_VIEW).apply {\n            addFlags(Intent.FLAG_ACTIVITY_NEW_TASK)\n            addFlags(Intent.FLAG_GRANT_READ_URI_PERMISSION)\n            setDataAndType(uri, \"application/vnd.android.package-archive\")\n        }\n        startActivity(intent)\n    }\n}\n"
  },
  {
    "path": "app/src/main/java/com/yenaly/han1meviewer/util/Views.kt",
    "content": "package com.yenaly.han1meviewer.util\n\nimport android.view.Gravity\nimport android.view.View\nimport android.view.ViewGroup\nimport android.widget.Button\nimport android.widget.TextView\nimport androidx.annotation.DrawableRes\nimport androidx.annotation.GravityInt\nimport androidx.annotation.Px\nimport androidx.core.view.children\nimport com.google.android.material.badge.BadgeDrawable\nimport com.google.android.material.tabs.TabLayout\n\nfun Button.setDrawableTop(@DrawableRes drawableRes: Int) {\n    this.setCompoundDrawablesWithIntrinsicBounds(0, drawableRes, 0, 0)\n}\n\nfun TabLayout.getTextViewAt(position: Int): TextView? {\n    val container = getChildAt(0) as? ViewGroup\n    val tab = container?.getChildAt(position) as? ViewGroup\n    return tab?.children?.filterIsInstance<TextView>()?.singleOrNull()\n}\n\nfun TabLayout.getOrCreateBadgeOnTextViewAt(\n    position: Int,\n    targetView: View?,\n    @GravityInt gravity: Int,\n    @Px spacing: Int = 0,\n    action: BadgeDrawable.() -> Unit\n) {\n    val tab = getTabAt(position) ?: return\n    val target = targetView ?: getTextViewAt(position)\n    target?.post {\n        tab.orCreateBadge.apply(action).apply {\n            setGravity(target, gravity, spacing)\n        }\n    }\n}\n\nfun BadgeDrawable.setGravity(\n    targetView: View,\n    @GravityInt gravity: Int,\n    @Px spacing: Int = 0\n) {\n    badgeGravity = BadgeDrawable.TOP_END\n    when (gravity) {\n        Gravity.START -> {\n            verticalOffset = (targetView.height + this.intrinsicHeight) / 2\n            horizontalOffset = targetView.width + this.intrinsicWidth + spacing\n        }\n\n        Gravity.END -> {\n            verticalOffset = (targetView.height + this.intrinsicHeight) / 2\n            horizontalOffset = -spacing\n        }\n\n        Gravity.BOTTOM -> {\n            verticalOffset = targetView.height + this.intrinsicHeight + spacing\n            horizontalOffset = (targetView.width + this.intrinsicWidth) / 2\n        }\n\n        Gravity.TOP -> {\n            verticalOffset = -spacing\n            horizontalOffset = (targetView.width + this.intrinsicWidth) / 2\n        }\n    }\n}"
  },
  {
    "path": "app/src/main/java/com/yenaly/han1meviewer/worker/HUpdateWorker.kt",
    "content": "package com.yenaly.han1meviewer.worker\n\nimport android.annotation.SuppressLint\nimport android.app.Notification\nimport android.content.Context\nimport android.content.pm.ServiceInfo\nimport android.os.Build\nimport android.util.Log\nimport androidx.core.app.NotificationCompat\nimport androidx.core.app.NotificationManagerCompat\nimport androidx.core.net.toFile\nimport androidx.core.net.toUri\nimport androidx.work.Constraints\nimport androidx.work.CoroutineWorker\nimport androidx.work.ExistingWorkPolicy\nimport androidx.work.ForegroundInfo\nimport androidx.work.NetworkType\nimport androidx.work.OneTimeWorkRequestBuilder\nimport androidx.work.WorkInfo\nimport androidx.work.WorkManager\nimport androidx.work.WorkerParameters\nimport androidx.work.await\nimport androidx.work.workDataOf\nimport com.yenaly.han1meviewer.EMPTY_STRING\nimport com.yenaly.han1meviewer.Preferences\nimport com.yenaly.han1meviewer.R\nimport com.yenaly.han1meviewer.UPDATE_NOTIFICATION_CHANNEL\nimport com.yenaly.han1meviewer.logic.DatabaseRepo\nimport com.yenaly.han1meviewer.logic.dao.DownloadDatabase\nimport com.yenaly.han1meviewer.logic.entity.download.HUpdateEntity\nimport com.yenaly.han1meviewer.logic.model.github.Latest\nimport com.yenaly.han1meviewer.logic.network.HUpdater\nimport com.yenaly.han1meviewer.logic.state.DownloadState\nimport com.yenaly.han1meviewer.util.UPDATE_ZIP_PATH\nimport com.yenaly.han1meviewer.util.installApkPackage\nimport com.yenaly.han1meviewer.util.runSuspendCatching\nimport com.yenaly.han1meviewer.util.updateFile\nimport com.yenaly.yenaly_libs.utils.applicationContext\nimport com.yenaly.yenaly_libs.utils.showShortToast\nimport kotlinx.coroutines.CoroutineScope\nimport kotlinx.coroutines.Dispatchers\nimport kotlinx.coroutines.SupervisorJob\nimport kotlinx.coroutines.launch\nimport okhttp3.internal.closeQuietly\nimport java.util.concurrent.CancellationException\nimport kotlin.random.Random\n\n/**\n * @project Han1meViewer\n * @author Yenaly Liew\n * @time 2024/03/22 022 21:27\n */\nclass HUpdateWorker(\n    private val context: Context,\n    workerParams: WorkerParameters,\n) : CoroutineWorker(context, workerParams), WorkerMixin {\n    companion object {\n        const val TAG = \"HUpdateWorker\"\n        private val workManager = WorkManager.getInstance(applicationContext)\n        private val hUpdateDao = DownloadDatabase.instance.hUpdateDao\n\n        val scope = CoroutineScope(SupervisorJob() + Dispatchers.IO)\n        private val mainScope = CoroutineScope(Dispatchers.Main.immediate)\n\n        const val DOWNLOAD_LINK = \"download_link\"\n        const val START_OFFSET = \"start_offset\"\n        const val NODE_ID = \"node_id\"\n        const val UPDATE_APK = \"update_apk\"\n\n        fun deleteUpdate(context: Context) {\n            deleteUpdateFile(context)\n            // 删除数据库中的更新记录\n            scope.launch {\n                hUpdateDao.delete()\n            }\n        }\n\n        fun deleteUpdateFile(context: Context) {\n            context.updateFile.delete()\n            UPDATE_ZIP_PATH.delete()\n        }\n        /**\n         * This function is used to enqueue a download task\n         */\n        suspend fun enqueue(context: Context, latest: Latest) {\n            deleteUpdateFile(context)\n\n            val entity = HUpdateEntity(\n                name = latest.version,\n                url = latest.downloadLink,\n                nodeId = latest.nodeId,\n                length = 1,\n                downloadedLength = 0,\n                state = DownloadState.Queued,\n            )\n            hUpdateDao.insert(entity)\n\n            val constraints = Constraints.Builder()\n                .setRequiredNetworkType(NetworkType.CONNECTED)\n                .build()\n            val data = workDataOf(\n                DOWNLOAD_LINK to latest.downloadLink,\n                NODE_ID to latest.nodeId,\n                START_OFFSET to entity.downloadedLength\n            )\n            val req = OneTimeWorkRequestBuilder<HUpdateWorker>()\n                .addTag(TAG)\n                .setConstraints(constraints)\n                .setInputData(data)\n                .build()\n            workManager\n                .beginUniqueWork(TAG, ExistingWorkPolicy.REPLACE, req)\n                .enqueue()\n        }\n\n        fun resume() {\n            scope.launch {\n                val entity = hUpdateDao.get() as HUpdateEntity\n                val data = workDataOf(\n                    DOWNLOAD_LINK to entity.url,\n                    NODE_ID to entity.nodeId,\n                    START_OFFSET to entity.downloadedLength\n                )\n                val constraints = Constraints.Builder()\n                    .setRequiredNetworkType(NetworkType.CONNECTED)\n                    .build()\n                val req = OneTimeWorkRequestBuilder<HUpdateWorker>()\n                    .addTag(TAG)\n                    .setConstraints(constraints)\n                    .setInputData(data)\n                    .build()\n                workManager\n                    .beginUniqueWork(TAG, ExistingWorkPolicy.REPLACE, req)\n                    .enqueue().await()\n            }\n        }\n\n        fun stop() {\n            scope.launch {\n                runSuspendCatching {\n                    workManager.cancelUniqueWork(TAG).await()\n                }.onFailure { t -> // 上述方法可能无法取消任务\n                    t.printStackTrace()\n                    // 通过替换任务实现任务删除（文件不删除），确保 WorkManager 真正取消\n                    val deleteRequest =\n                        OneTimeWorkRequestBuilder<HanimeDownloadWorker>()\n                            .addTag(TAG)\n                            .setInputData(workDataOf(HanimeDownloadWorker.FAST_PATH_CANCEL to true))\n                            .build()\n                    workManager.beginUniqueWork(\n                        TAG, ExistingWorkPolicy.REPLACE, deleteRequest\n                    ).enqueue().await()\n                }\n            }\n        }\n\n        /**\n         * This function is used to collect the output of the download task\n         */\n        suspend fun collectOutput(context: Context) = WorkManager.getInstance(context)\n            .getWorkInfosByTagFlow(TAG)\n            .collect { workInfos ->\n                // 只有一個！\n                val workInfo = workInfos.firstOrNull()\n                workInfo?.let {\n                    when (it.state) {\n                        WorkInfo.State.SUCCEEDED -> {\n                            val apkPath = it.outputData.getString(UPDATE_APK)\n                            val file = apkPath?.toUri()?.toFile()\n                            file?.let { context.installApkPackage(file) }\n                        }\n\n                        WorkInfo.State.FAILED -> {\n                            showShortToast(R.string.update_failed)\n                        }\n\n                        else -> Unit\n                    }\n                }\n            }\n    }\n\n    private val notificationManager = NotificationManagerCompat.from(context)\n\n    private val downloadLink by inputData(DOWNLOAD_LINK, EMPTY_STRING)\n    private val startOffset by inputData(START_OFFSET, 0L)\n    private val nodeId by inputData(NODE_ID, EMPTY_STRING)\n    private val downloadId = Random.nextInt()\n\n    override suspend fun doWork(): Result {\n        with(HUpdater) {\n            var result: Result = Result.failure()\n            val file = context.updateFile\n            try {\n                setForeground(createForegroundInfo())\n                Log.i(TAG, \"doWork: $startOffset\")\n                file.injectUpdate(downloadLink, startOffset) { progress ->\n                    updateNotification(progress)\n                }\n                val outputData = workDataOf(\n                    UPDATE_APK to file.toUri().toString(),\n                    DownloadState.STATE to DownloadState.Finished.mask\n                )\n                Preferences.updateNodeId = nodeId\n                result =  Result.success(outputData)\n            } catch (e: Exception) {\n                result = if (e is CancellationException) {\n                    // cancellation exception block 是代表用户暂停\n//                    cancelDownloadNotification()\n                    Result.success(\n                        workDataOf(DownloadState.STATE to DownloadState.Paused.mask)\n                    )\n                } else {\n//                    showFailureNotification(e.localizedMessage)\n                    e.printStackTrace()\n                    mainScope.launch {\n                        showShortToast(e.localizedMessage)\n                    }\n                    file.delete()\n                    Result.failure(\n                        workDataOf(DownloadState.STATE to DownloadState.Failed.mask)\n                    )\n                }\n            } finally {\n                val state = DownloadState.from(\n                    result.outputData.getInt(DownloadState.STATE, DownloadState.Unknown.mask)\n                )\n                // 为什么要用 dbScope 包住？\n                // 使用 dbScope 是为了确保即使当前协程因任务取消而失效，\n                // “update”挂起函数仍然能够找到有效的协程作用域来更新数据库。\n                // 这也是一个历史遗留问题。\n                scope.launch {\n                    hUpdateDao.updateState(state)\n                }\n            }\n            return result\n        }\n    }\n\n    private fun createNotification(progress: Int = 0): Notification {\n        return NotificationCompat.Builder(context, UPDATE_NOTIFICATION_CHANNEL)\n            .setSmallIcon(R.mipmap.ic_launcher)\n            .setOngoing(true)\n            .setContentTitle(context.getString(R.string.downloading_update_percent, progress))\n            .setPriority(NotificationCompat.PRIORITY_HIGH)\n            .setOnlyAlertOnce(true)\n            .setProgress(100, progress, false)\n            .build()\n    }\n\n    @SuppressLint(\"MissingPermission\")\n    private fun updateNotification(progress: Int) {\n        notificationManager.notify(downloadId, createNotification(progress))\n    }\n\n    private fun createForegroundInfo(progress: Int = 0): ForegroundInfo {\n        return ForegroundInfo(\n            downloadId,\n            createNotification(progress),\n            if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.Q) {\n                ServiceInfo.FOREGROUND_SERVICE_TYPE_DATA_SYNC\n            } else 0\n        )\n    }\n}"
  },
  {
    "path": "app/src/main/java/com/yenaly/han1meviewer/worker/HanimeDownloadManager.kt",
    "content": "package com.yenaly.han1meviewer.worker\n\nimport android.util.Log\nimport androidx.work.ExistingWorkPolicy\nimport androidx.work.OneTimeWorkRequestBuilder\nimport androidx.work.Operation\nimport androidx.work.WorkManager\nimport androidx.work.await\nimport androidx.work.workDataOf\nimport com.yenaly.han1meviewer.Preferences\nimport com.yenaly.han1meviewer.logic.dao.DownloadDatabase\nimport com.yenaly.han1meviewer.logic.entity.download.HanimeDownloadEntity\nimport com.yenaly.han1meviewer.util.runSuspendCatching\nimport com.yenaly.yenaly_libs.utils.applicationContext\nimport java.util.Queue\nimport java.util.concurrent.ConcurrentHashMap\nimport java.util.concurrent.ConcurrentLinkedQueue\n\n@Deprecated(\"使用 HanimeDownloadWorkerV2\")\nobject HanimeDownloadManager {\n\n    const val TAG = \"HanimeDownloadManager\"\n    private const val DOWNLOADING = \"downloading\"\n    private const val WAITING = \"waiting\"\n\n    const val MAX_CONCURRENT_DOWNLOAD_DEF = 2\n    var maxConcurrentDownloadCount = Preferences.downloadCountLimit\n        set(value) {\n            field = if (value > 0) value else Int.MAX_VALUE\n        }\n\n    private val downloadingQueue = DownloadQueue.withTag(DOWNLOADING)\n    private val waitingQueue = DownloadQueue.withTag(WAITING)\n\n    private val workManager = WorkManager.getInstance(applicationContext)\n\n    /**\n     * 用于程序初始化时加载所有正在下载的任务\n     */\n    suspend fun init() {\n        Log.d(TAG, \"init\")\n        val all = DownloadDatabase.instance.hanimeDownloadDao.loadAllDownloadingHanimeOnce()\n        // 前 maxConcurrentDownloadCount 个任务直接下载，后面的任务加入等待队列\n        for (i in all.indices) {\n            if (i < maxConcurrentDownloadCount) {\n                addTask(HanimeDownloadWorker.Args.fromEntity(all[i]), redownload = false)\n            } else {\n                waitingQueue.offer(HanimeDownloadWorker.Args.fromEntity(all[i]))\n            }\n        }\n    }\n\n    /**\n     * 通知下载任务完成，开始下一个在等待队列中的任务\n     */\n    internal fun notify(entity: HanimeDownloadEntity) = notify(\n        HanimeDownloadWorker.Args.fromEntity(entity)\n    )\n\n    /**\n     * 通知下载任务完成，开始下一个在等待队列中的任务\n     */\n    private fun notify(args: HanimeDownloadWorker.Args) {\n        when (args) {\n            in downloadingQueue -> {\n                downloadingQueue.remove(args)\n                waitingQueue.poll()?.let { waitingTask ->\n                    addTask(waitingTask, redownload = false)\n                }\n            }\n\n            in waitingQueue -> {\n                waitingQueue.remove(args)\n            }\n        }\n    }\n\n    /**\n     * 添加下载任务，如果任务正在下载中，则不会重复添加；\n     * 如果下载队列已满，则会加入等待队列。\n     */\n    fun addTask(args: HanimeDownloadWorker.Args, redownload: Boolean = false) {\n        Log.d(TAG, \"addTask: $args\")\n        if (args in downloadingQueue) {\n            return\n        }\n        if (downloadingQueue.size < maxConcurrentDownloadCount) {\n            // 添加新任务\n            downloadingQueue.offer(args)\n            startWork(args, redownload = redownload)\n        } else {\n            waitingQueue.offer(args)\n            startWork(args, redownload = redownload, waiting = true)\n        }\n    }\n\n    /**\n     * 恢复下载任务，如果任务已经在下载队列中，则不会重复添加；\n     * 如果下载队列已满，则会将一个正在下载的任务放到等待队列，\n     * 然后将 [entity] 加入下载队列。\n     */\n    fun resumeTask(entity: HanimeDownloadEntity) = resumeTask(\n        HanimeDownloadWorker.Args.fromEntity(entity)\n    )\n\n    /**\n     * 恢复下载任务，如果任务已经在下载队列中，则不会重复添加；\n     * 如果下载队列已满，则会将一个正在下载的任务放到等待队列，\n     * 然后将 [args] 加入下载队列。\n     */\n    fun resumeTask(args: HanimeDownloadWorker.Args) {\n        Log.d(TAG, \"resumeTask: $args\")\n        when (args) {\n            in downloadingQueue -> return\n            else -> {\n                while (downloadingQueue.size >= maxConcurrentDownloadCount) {\n                    downloadingQueue.poll()?.let { dead ->\n                        stopTask(dead)\n                        waitingQueue.offer(dead)\n                    }\n                }\n                waitingQueue.remove(args)\n                addTask(args, redownload = false)\n            }\n        }\n    }\n\n    /**\n     * 停止下载任务，如果任务正在下载中，则会立即停止；\n     * 如果任务在等待队列中，则会从等待队列中移除。\n     */\n    fun stopTask(args: HanimeDownloadWorker.Args): Operation {\n        Log.d(TAG, \"stopTask: $args\")\n        notify(args)\n        return stopWork(args)\n    }\n\n    /**\n     * 停止下载任务，如果任务正在下载中，则会立即停止；\n     * 如果任务在等待队列中，则会从等待队列中移除。\n     */\n    fun stopTask(entity: HanimeDownloadEntity): Operation = stopTask(\n        HanimeDownloadWorker.Args.fromEntity(entity)\n    )\n\n    /**\n     * 删除下载任务，如果任务正在下载中，则会立即停止并删除；\n     * 如果任务在等待队列中，则会从等待队列中移除。\n     */\n    suspend fun deleteTaskCrazily(entity: HanimeDownloadEntity) {\n        Log.d(TAG, \"deleteTask: ${entity.videoCode}\")\n        // 必須另闢蹊徑，通過替換的方式來刪除，要不然無法真正地取消。\n        val downloadRequest = OneTimeWorkRequestBuilder<HanimeDownloadWorker>()\n            .addTag(HanimeDownloadWorker.TAG)\n            .setInputData(workDataOf(HanimeDownloadWorker.FAST_PATH_CANCEL to true))\n            .build()\n        runSuspendCatching {\n            workManager.beginUniqueWork(\n                entity.videoCode, ExistingWorkPolicy.REPLACE, downloadRequest\n            ).enqueue().await()\n        }.onSuccess {\n            notify(entity)\n        }\n    }\n\n    /**\n     * 获取下载状态\n     */\n    fun getDownloadState(videoCode: String): DownloadState {\n        return when (videoCode) {\n            in downloadingQueue -> DownloadState.Downloading\n            in waitingQueue -> DownloadState.Waiting\n            else -> DownloadState.Stopped\n        }\n    }\n\n    private fun stopWork(args: HanimeDownloadWorker.Args): Operation {\n        return workManager.cancelUniqueWork(args.videoCode)\n    }\n\n    private fun startWork(\n        args: HanimeDownloadWorker.Args,\n        redownload: Boolean = false,\n        waiting: Boolean = false\n    ) {\n        HanimeDownloadWorker.build {\n            setInputData(\n                workDataOf(\n                    HanimeDownloadWorker.QUALITY to args.quality,\n                    HanimeDownloadWorker.DOWNLOAD_URL to args.downloadUrl,\n                    HanimeDownloadWorker.VIDEO_TYPE to args.videoType,\n                    HanimeDownloadWorker.HANIME_NAME to args.hanimeName,\n                    HanimeDownloadWorker.VIDEO_CODE to args.videoCode,\n                    HanimeDownloadWorker.COVER_URL to args.coverUrl,\n                    HanimeDownloadWorker.REDOWNLOAD to redownload,\n                    HanimeDownloadWorker.IN_WAITING_QUEUE to waiting\n                )\n            )\n        }.apply {\n            workManager.beginUniqueWork(\n                args.videoCode, ExistingWorkPolicy.REPLACE, this\n            ).enqueue()\n        }\n    }\n\n    enum class DownloadState {\n        Downloading, Waiting, Stopped\n    }\n\n    private class DownloadQueue private constructor(private val tag: String) {\n\n        companion object {\n            @JvmStatic\n            fun withTag(tag: String) = DownloadQueue(\"DownloadQueue-$tag\")\n        }\n\n        private val codeSet: MutableSet<String> = ConcurrentHashMap.newKeySet()\n        private val queue: Queue<HanimeDownloadWorker.Args> = ConcurrentLinkedQueue()\n\n        val size: Int get() = queue.size\n\n        fun offer(e: HanimeDownloadWorker.Args) {\n            codeSet += e.videoCode\n            queue.offer(e)\n            Log.d(tag, codeSet.toString())\n        }\n\n        fun poll(): HanimeDownloadWorker.Args? {\n            val poll = queue.poll()\n            if (poll != null) {\n                codeSet -= poll.videoCode\n            }\n            Log.d(tag, codeSet.toString())\n            return poll\n        }\n\n        fun remove(o: HanimeDownloadWorker.Args?): Boolean {\n            if (o != null && codeSet.remove(o.videoCode)) {\n                val r = queue.remove(o)\n                Log.d(tag, codeSet.toString())\n                return r\n            }\n            return false\n        }\n\n        operator fun contains(o: HanimeDownloadWorker.Args?): Boolean {\n            return o != null && o.videoCode in codeSet\n        }\n\n        operator fun contains(videoCode: String): Boolean {\n            return videoCode in codeSet\n        }\n    }\n}"
  },
  {
    "path": "app/src/main/java/com/yenaly/han1meviewer/worker/HanimeDownloadManagerV2.kt",
    "content": "package com.yenaly.han1meviewer.worker\n\nimport android.util.Log\nimport androidx.lifecycle.Observer\nimport androidx.work.ExistingWorkPolicy\nimport androidx.work.OneTimeWorkRequestBuilder\nimport androidx.work.WorkInfo\nimport androidx.work.WorkManager\nimport androidx.work.await\nimport androidx.work.workDataOf\nimport com.yenaly.han1meviewer.Preferences\nimport com.yenaly.han1meviewer.logic.dao.DownloadDatabase\nimport com.yenaly.han1meviewer.logic.entity.download.HanimeDownloadEntity\nimport com.yenaly.han1meviewer.logic.state.DownloadState\nimport com.yenaly.han1meviewer.util.runSuspendCatching\nimport com.yenaly.yenaly_libs.utils.applicationContext\nimport kotlinx.coroutines.CoroutineScope\nimport kotlinx.coroutines.Dispatchers\nimport kotlinx.coroutines.SupervisorJob\nimport kotlinx.coroutines.channels.Channel\nimport kotlinx.coroutines.launch\nimport kotlinx.coroutines.suspendCancellableCoroutine\nimport kotlinx.coroutines.sync.Semaphore\nimport kotlinx.coroutines.sync.withPermit\nimport kotlin.coroutines.resume\n\n/**\n * 优化后的下载管理器，利用 Channel 和 Semaphore 限制并发下载数，\n * 同时通过监听 WorkManager 的任务状态实现“等待任务完成后释放许可”的逻辑。\n */\nobject HanimeDownloadManagerV2 {\n\n    private const val TAG = \"HanimeDownloadManager\"\n\n    const val MAX_CONCURRENT_DOWNLOAD_DEF = 2\n    var maxConcurrentDownloadCount = Preferences.downloadCountLimit\n        set(value) {\n            field = if (value > 0) value else Int.MAX_VALUE\n            // 如果更新并发数，重新创建 semaphore\n            semaphore = Semaphore(field)\n        }\n\n    private val workManager = WorkManager.getInstance(applicationContext)\n\n    // 信号量限制同时下载的任务数量\n    private var semaphore = Semaphore(maxConcurrentDownloadCount)\n\n    // Channel 内部状态：保存正在下载任务与等待队列\n    private val activeDownloads = linkedMapOf<String, HanimeDownloadWorker.Args>()\n    private val waitingQueue = ArrayDeque<HanimeDownloadWorker.Args>()\n\n    // 协程 Scope，用于管理 channel 与任务协程\n    private val scope = CoroutineScope(SupervisorJob() + Dispatchers.IO)\n\n    // Channel 消息类型\n    private sealed class DownloadMsg {\n        /**\n         * 添加下载任务\n         */\n        data class Add(\n            val args: HanimeDownloadWorker.Args,\n            val redownload: Boolean = false,\n            val waiting: Boolean = false,\n            val state: DownloadState = DownloadState.Unknown\n        ) : DownloadMsg()\n\n        /**\n         * 恢复下载任务（暂停 => 下载）\n         */\n        data class Resume(val args: HanimeDownloadWorker.Args) : DownloadMsg()\n\n        /**\n         * 停止下载任务\n         */\n        data class Stop(val args: HanimeDownloadWorker.Args) : DownloadMsg()\n\n        /**\n         * 删除下载任务\n         */\n        data class Delete(val args: HanimeDownloadWorker.Args) : DownloadMsg()\n\n        /**\n         * 处理下一个任务\n         */\n        data object ProcessNext : DownloadMsg()\n    }\n\n    private val downloadChannel = Channel<DownloadMsg>(capacity = Channel.UNLIMITED)\n\n    init {\n        scope.launch {\n            for (msg in downloadChannel) {\n                when (msg) {\n                    is DownloadMsg.Add -> {\n                        if (msg.args.videoCode in activeDownloads) {\n                            Log.d(TAG, \"任务已存在：${msg.args.videoCode}\")\n                        } else {\n                            // Unknown 代表任务刚添加，未开始状态流转\n                            if (activeDownloads.size < maxConcurrentDownloadCount &&\n                                (msg.state == DownloadState.Downloading || msg.state == DownloadState.Unknown)\n                            ) {\n                                Log.d(TAG, \"添加任务：${msg.args.videoCode}\")\n                                activeDownloads[msg.args.videoCode] = msg.args\n                                launchDownload(msg.args, msg.redownload, msg.waiting)\n                            } else {\n                                Log.d(TAG, \"任务已满，加入等待队列：${msg.args.videoCode}\")\n                                when (msg.state) {\n                                    DownloadState.Downloading -> {\n                                        // 之前为 Downloading 的优先级更高\n                                        waitingQueue.addFirst(msg.args)\n                                        // 同时启动 WorkManager 任务时可标识为等待状态\n                                        enqueueWork(msg.args, msg.redownload)\n                                    }\n\n                                    DownloadState.Queued, DownloadState.Unknown -> {\n                                        waitingQueue.addLast(msg.args)\n                                        // 同时启动 WorkManager 任务时可标识为等待状态\n                                        enqueueWork(msg.args, msg.redownload)\n                                    }\n\n                                    else -> Unit\n                                }\n                            }\n                        }\n                    }\n\n                    is DownloadMsg.Resume -> {\n                        if (msg.args.videoCode in activeDownloads) {\n                            Log.d(TAG, \"任务已在下载中，无需恢复：${msg.args.videoCode}\")\n                        } else {\n                            waitingQueue.removeIf { it.videoCode == msg.args.videoCode }\n                            Log.d(TAG, \"恢复任务：${msg.args.videoCode}\")\n                            // 如果 active 已满，则暂停一个任务，加入等待队列\n                            while (activeDownloads.size >= maxConcurrentDownloadCount && activeDownloads.isNotEmpty()) {\n                                val (videoCode, task) = activeDownloads.entries.first()\n                                activeDownloads.remove(videoCode)\n                                enqueueWork(task)\n                                waitingQueue.addLast(task)\n                                Log.d(TAG, \"任务已满，暂停任务：$videoCode\")\n                            }\n                            activeDownloads[msg.args.videoCode] = msg.args\n                            launchDownload(msg.args, redownload = false, waiting = false)\n                        }\n                    }\n\n                    is DownloadMsg.Stop -> {\n                        if (activeDownloads.remove(msg.args.videoCode) != null) {\n                            Log.d(TAG, \"停止任务：${msg.args.videoCode}\")\n                            stopWork(msg.args)\n                            processNext()\n                        } else {\n                            Log.e(TAG, \"停止任务，不应该走到这里：${msg.args.videoCode}\")\n//                            waitingQueue.removeIf { it.videoCode == msg.args.videoCode }\n                        }\n                    }\n\n                    is DownloadMsg.Delete -> {\n                        if (activeDownloads.remove(msg.args.videoCode) != null) {\n                            Log.d(TAG, \"从正在下载列表中删除任务：${msg.args.videoCode}\")\n                        } else {\n                            waitingQueue.removeIf { it.videoCode == msg.args.videoCode }\n                            Log.d(TAG, \"从等待队列中删除任务：${msg.args.videoCode}\")\n                        }\n                        deleteWork(msg.args)\n                        processNext()\n                    }\n\n                    DownloadMsg.ProcessNext -> processNext()\n                }\n            }\n        }\n    }\n\n    /**\n     * 初始化，加载所有正在下载的任务\n     */\n    suspend fun init() {\n        Log.d(TAG, \"init\")\n        val allDownloading =\n            DownloadDatabase.instance.hanimeDownloadDao.loadAllDownloadingHanimeOnce()\n        allDownloading.forEach { entity ->\n            val args = HanimeDownloadWorker.Args.fromEntity(entity)\n            // addTask\n            downloadChannel.send(DownloadMsg.Add(args, state = entity.state))\n        }\n    }\n\n    /**\n     * 添加下载任务\n     */\n    fun addTask(\n        args: HanimeDownloadWorker.Args,\n        redownload: Boolean = false, waiting: Boolean = false\n    ) {\n        scope.launch { downloadChannel.send(DownloadMsg.Add(args, redownload, waiting)) }\n    }\n\n    /**\n     * 恢复下载任务\n     */\n    fun resumeTask(entity: HanimeDownloadEntity) {\n        val args = HanimeDownloadWorker.Args.fromEntity(entity)\n        scope.launch { downloadChannel.send(DownloadMsg.Resume(args)) }\n    }\n\n    /**\n     * 停止下载任务\n     */\n    fun stopTask(entity: HanimeDownloadEntity) {\n        val args = HanimeDownloadWorker.Args.fromEntity(entity)\n        scope.launch { downloadChannel.send(DownloadMsg.Stop(args)) }\n    }\n\n    /**\n     * 删除下载任务\n     */\n    fun deleteTask(entity: HanimeDownloadEntity) {\n        val args = HanimeDownloadWorker.Args.fromEntity(entity)\n        scope.launch { downloadChannel.send(DownloadMsg.Delete(args)) }\n    }\n\n    /**\n     * 处理等待队列中的下一个任务\n     */\n    private fun processNext() {\n        Log.d(TAG, \"processNext\")\n        while (activeDownloads.size < maxConcurrentDownloadCount && waitingQueue.isNotEmpty()) {\n            val next = waitingQueue.removeFirst()\n            activeDownloads[next.videoCode] = next\n            launchDownload(next, redownload = false, waiting = false)\n        }\n    }\n\n    /**\n     * 启动下载任务，采用 semaphore 限制并发数，并等待任务完成后自动释放许可\n     */\n    private fun launchDownload(\n        args: HanimeDownloadWorker.Args,\n        redownload: Boolean,\n        waiting: Boolean\n    ) {\n        scope.launch {\n            // 如果当前处于等待状态，则直接启动任务。目的就是为了添加到列表，但不下载\n            if (waiting) {\n                Log.d(TAG, \"launchDownload (waiting): ${args.videoCode}\")\n                enqueueWork(args, redownload)\n            } else {\n                // 使用 semaphore.withPermit 来确保同时只有规定数量的任务在执行\n                semaphore.withPermit {\n                    Log.d(TAG, \"launchDownload (start): ${args.videoCode}\")\n                    // 启动 WorkManager 任务\n                    startWork(args, redownload)\n                    // 阻塞等待 WorkManager 任务完成\n                    awaitWorkCompletion(args.videoCode)\n                }\n                // 下载完成或取消后，从 active 中移除，并尝试启动下一个任务\n                activeDownloads.remove(args.videoCode)\n                Log.d(TAG, \"launchDownload (end): ${args.videoCode}\")\n                downloadChannel.send(DownloadMsg.ProcessNext)\n            }\n        }\n    }\n\n    /**\n     * 开启下载任务\n     */\n    private suspend fun startWork(\n        args: HanimeDownloadWorker.Args,\n        redownload: Boolean = false,\n        waiting: Boolean = false,\n        delete: Boolean = false\n    ) {\n        HanimeDownloadWorker.build(constraintsRequired = !delete) {\n            setInputData(\n                workDataOf(\n                    HanimeDownloadWorker.QUALITY to args.quality,\n                    HanimeDownloadWorker.DOWNLOAD_URL to args.downloadUrl,\n                    HanimeDownloadWorker.VIDEO_TYPE to args.videoType,\n                    HanimeDownloadWorker.HANIME_NAME to args.hanimeName,\n                    HanimeDownloadWorker.VIDEO_CODE to args.videoCode,\n                    HanimeDownloadWorker.COVER_URL to args.coverUrl,\n                    HanimeDownloadWorker.REDOWNLOAD to redownload,\n                    HanimeDownloadWorker.IN_WAITING_QUEUE to waiting,\n                    HanimeDownloadWorker.DELETE to delete\n                )\n            )\n        }.apply {\n            workManager.beginUniqueWork(\n                args.videoCode, ExistingWorkPolicy.REPLACE, this\n            ).enqueue().await()\n        }\n    }\n\n    /**\n     * 取消正在执行的 WorkManager 任务\n     */\n    private suspend fun stopWork(args: HanimeDownloadWorker.Args) {\n        runSuspendCatching {\n            workManager.cancelUniqueWork(args.videoCode).await()\n            Log.d(TAG, \"stopWork (cancelUniqueWork): ${args.videoCode}\")\n        }.onFailure { t -> // 上述方法可能无法取消任务\n            t.printStackTrace()\n            // 通过替换任务实现任务删除（文件不删除），确保 WorkManager 真正取消\n            val deleteRequest =\n                OneTimeWorkRequestBuilder<HanimeDownloadWorker>()\n                    .addTag(HanimeDownloadWorker.TAG)\n                    .setInputData(workDataOf(HanimeDownloadWorker.FAST_PATH_CANCEL to true))\n                    .build()\n            workManager.beginUniqueWork(\n                args.videoCode, ExistingWorkPolicy.REPLACE, deleteRequest\n            ).enqueue().await()\n            Log.d(TAG, \"stopWork (delete request): ${args.videoCode}\")\n        }\n    }\n\n    /**\n     * 删除下载任务\n     *\n     * 删除操作交给 WorkManager 处理\n     */\n    private suspend fun deleteWork(args: HanimeDownloadWorker.Args) = startWork(args, delete = true)\n\n    /**\n     * 将下载任务加入等待队列\n     *\n     * 操作交给 WorkManager 处理\n     */\n    private suspend fun enqueueWork(args: HanimeDownloadWorker.Args, redownload: Boolean = false) =\n        startWork(args, redownload = redownload, waiting = true)\n\n    /**\n     * 通过观察 WorkManager 的 LiveData 来阻塞等待任务完成\n     */\n    private suspend fun awaitWorkCompletion(videoCode: String) =\n        suspendCancellableCoroutine { cont ->\n            val liveData = workManager.getWorkInfosForUniqueWorkLiveData(videoCode)\n            Log.d(TAG, \"获取 LiveData：$videoCode\")\n            val observer = object : Observer<List<WorkInfo>> {\n                override fun onChanged(value: List<WorkInfo>) {\n                    val info = value.firstOrNull() ?: return\n                    if (info.state.isFinished) {\n                        Log.d(TAG, \"任务完成，移除 observer：$videoCode\")\n                        liveData.removeObserver(this)\n                        cont.resume(Unit)\n                    }\n                }\n            }\n            CoroutineScope(Dispatchers.Main).launch {\n                liveData.observeForever(observer)\n                Log.d(TAG, \"添加 observer：$videoCode\")\n            }\n            cont.invokeOnCancellation { liveData.removeObserver(observer) }\n        }\n}\n"
  },
  {
    "path": "app/src/main/java/com/yenaly/han1meviewer/worker/HanimeDownloadWorker.kt",
    "content": "package com.yenaly.han1meviewer.worker\n\nimport android.annotation.SuppressLint\nimport android.app.Notification\nimport android.content.Context\nimport android.content.pm.ServiceInfo\nimport android.os.Build\nimport android.util.Log\nimport androidx.core.app.NotificationCompat\nimport androidx.core.app.NotificationManagerCompat\nimport androidx.core.net.toUri\nimport androidx.work.BackoffPolicy\nimport androidx.work.Constraints\nimport androidx.work.CoroutineWorker\nimport androidx.work.ForegroundInfo\nimport androidx.work.NetworkType\nimport androidx.work.OneTimeWorkRequest\nimport androidx.work.OneTimeWorkRequestBuilder\nimport androidx.work.WorkInfo\nimport androidx.work.WorkManager\nimport androidx.work.WorkerParameters\nimport androidx.work.workDataOf\nimport com.yenaly.han1meviewer.DOWNLOAD_NOTIFICATION_CHANNEL\nimport com.yenaly.han1meviewer.EMPTY_STRING\nimport com.yenaly.han1meviewer.HFileManager\nimport com.yenaly.han1meviewer.R\nimport com.yenaly.han1meviewer.logic.DatabaseRepo\nimport com.yenaly.han1meviewer.logic.entity.download.HanimeDownloadEntity\nimport com.yenaly.han1meviewer.logic.network.ServiceCreator\nimport com.yenaly.han1meviewer.logic.state.DownloadState\nimport com.yenaly.han1meviewer.util.HImageMeower\nimport com.yenaly.han1meviewer.util.await\nimport com.yenaly.yenaly_libs.utils.createFileIfNotExists\nimport com.yenaly.yenaly_libs.utils.saveTo\nimport com.yenaly.yenaly_libs.utils.showShortToast\nimport kotlinx.coroutines.CoroutineScope\nimport kotlinx.coroutines.Dispatchers\nimport kotlinx.coroutines.flow.Flow\nimport kotlinx.coroutines.flow.distinctUntilChanged\nimport kotlinx.coroutines.flow.map\nimport kotlinx.coroutines.launch\nimport kotlinx.coroutines.withContext\nimport okhttp3.Request\nimport okhttp3.Response\nimport okhttp3.ResponseBody\nimport okhttp3.internal.closeQuietly\nimport java.io.File\nimport java.io.InputStream\nimport java.io.RandomAccessFile\nimport java.util.concurrent.CancellationException\nimport java.util.concurrent.TimeUnit\nimport kotlin.random.Random\n\n/**\n * @project Han1meViewer\n * @author Yenaly Liew\n * @time 2022/08/06 006 11:42\n */\nclass HanimeDownloadWorker(\n    private val context: Context,\n    workerParams: WorkerParameters,\n) : CoroutineWorker(context, workerParams), WorkerMixin {\n\n    data class Args(\n        val quality: String?,\n        val downloadUrl: String?,\n        val videoType: String?,\n        val hanimeName: String,\n        val videoCode: String,\n        val coverUrl: String,\n    ) {\n        companion object {\n            fun fromEntity(entity: HanimeDownloadEntity): Args {\n                return Args(\n                    quality = entity.quality,\n                    downloadUrl = entity.videoUrl,\n                    videoType = entity.suffix,\n                    hanimeName = entity.title,\n                    videoCode = entity.videoCode,\n                    coverUrl = entity.coverUrl,\n                )\n            }\n        }\n    }\n\n    companion object {\n        const val TAG = \"HanimeDownloadWorker\"\n\n        const val RESPONSE_INTERVAL = 500L\n\n        const val BACKOFF_DELAY = 10_000L\n\n        const val FAST_PATH_CANCEL = \"fast_path_cancel\"\n        const val DELETE = \"delete\"\n        const val QUALITY = \"quality\"\n        const val DOWNLOAD_URL = \"download_url\"\n        const val VIDEO_TYPE = \"video_type\"\n        const val HANIME_NAME = \"hanime_name\"\n        const val VIDEO_CODE = \"video_code\"\n        const val COVER_URL = \"cover_url\"\n        const val REDOWNLOAD = \"redownload\"\n        const val IN_WAITING_QUEUE = \"in_waiting_queue\"\n        // const val RELEASE_DATE = \"release_date\"\n        // const val COVER_DOWNLOAD = \"cover_download\"\n\n        const val PROGRESS = \"progress\"\n        // const val FAILED_REASON = \"failed_reason\"\n\n        /**\n         * 方便统一管理下载 Worker 的创建\n         */\n        inline fun build(\n            constraintsRequired: Boolean = true,\n            action: OneTimeWorkRequest.Builder.() -> Unit = {}\n        ): OneTimeWorkRequest {\n            val constraints = Constraints.Builder()\n                .setRequiredNetworkType(NetworkType.CONNECTED)\n                .setRequiresStorageNotLow(true)\n                .build()\n            return OneTimeWorkRequestBuilder<HanimeDownloadWorker>()\n                .addTag(TAG)\n                .let { builder ->\n                    if (constraintsRequired) {\n                        builder.setConstraints(constraints)\n                    } else {\n                        builder\n                    }\n                }.setBackoffCriteria(\n                    BackoffPolicy.LINEAR,\n                    BACKOFF_DELAY, TimeUnit.MILLISECONDS\n                ).apply(action).build()\n        }\n\n        fun getRunningWorkInfoCount(context: Context): Flow<Int> {\n            return WorkManager.getInstance(context)\n                .getWorkInfosByTagFlow(TAG)\n                .map { workInfos ->\n                    workInfos.count {\n                        it.state == WorkInfo.State.RUNNING\n                    }\n                }.distinctUntilChanged()\n        }\n    }\n\n    private val notificationManager = NotificationManagerCompat.from(context)\n\n    private val hanimeName by inputData(HANIME_NAME, EMPTY_STRING)\n    private val downloadUrl by inputData(DOWNLOAD_URL, EMPTY_STRING)\n    private val videoType by inputData(VIDEO_TYPE, HFileManager.DEF_VIDEO_TYPE)\n    private val quality by inputData(QUALITY, EMPTY_STRING)\n    private val videoCode by inputData(VIDEO_CODE, EMPTY_STRING)\n    private val coverUrl by inputData(COVER_URL, EMPTY_STRING)\n\n    private val fastPathCancel by inputData(FAST_PATH_CANCEL, false)\n    private val shouldDelete by inputData(DELETE, false)\n    private val shouldRedownload by inputData(REDOWNLOAD, false)\n    private val isInWaitingQueue by inputData(IN_WAITING_QUEUE, false)\n\n    private val downloadId = Random.nextInt()\n\n    private val mainScope = CoroutineScope(Dispatchers.Main.immediate)\n    private val dbScope = CoroutineScope(Dispatchers.IO)\n\n    override suspend fun doWork(): Result {\n        if (fastPathCancel) return Result.success()\n        setForeground(createForegroundInfo())\n        return download()\n    }\n\n    private suspend fun createNewRaf(file: File) {\n        return withContext(Dispatchers.IO) {\n            var raf: RandomAccessFile? = null\n            var response: Response? = null\n            var body: ResponseBody? = null\n            try {\n                file.createFileIfNotExists()\n                raf = RandomAccessFile(file, \"rwd\")\n                val request = Request.Builder().url(downloadUrl).get().build()\n                response = ServiceCreator.downloadClient.newCall(request).await()\n                if (response.isSuccessful) {\n                    body = response.body\n                    body?.let {\n                        val len = body.contentLength()\n                        if (len > 0) {\n                            raf.setLength(len)\n                            val entity = HanimeDownloadEntity(\n                                // 创建文件时不需要下载 coverImage\n                                coverUrl = coverUrl, coverUri = null,\n                                title = hanimeName,\n                                addDate = System.currentTimeMillis(), videoCode = videoCode,\n                                videoUri = file.toUri().toString(), quality = quality,\n                                videoUrl = downloadUrl, length = len, downloadedLength = 0,\n                                // isDownloading = false\n                                state = DownloadState.Queued\n                            )\n                            DatabaseRepo.HanimeDownload.insert(entity)\n                        }\n                    }\n                }\n            } catch (e: Exception) {\n                // 创建，但是并没有下载接收到文件大小，删除文件\n                if (file.exists() && file.length() == 0L) {\n                    // HFileManager.getDownloadVideoFolder(videoCode).deleteRecursively()\n                    // 不应该直接删除文件夹，因为可能存在其他分辨率的文件\n                    dbScope.launch {\n//                        val count = DatabaseRepo.HanimeDownload.countBy(videoCode)\n//                        HFileManager.deleteDownload(videoCode, count, file)\n                        HFileManager.getDownloadVideoFolder(videoCode).deleteRecursively()\n                    }\n                }\n                e.printStackTrace()\n            } finally {\n                raf?.closeQuietly()\n                response?.closeQuietly()\n                body?.closeQuietly()\n            }\n        }\n    }\n\n    private suspend fun download(): Result {\n        return withContext(Dispatchers.IO) {\n            val file = HFileManager.getDownloadVideoFile(\n                videoCode, hanimeName, quality, suffix = videoType\n            )\n            // redownload 不一定要删除全部文件夹，因为可能有不同分辨率\n            if (shouldRedownload || shouldDelete) {\n                // 注意顺序\n//                val count = DatabaseRepo.HanimeDownload.countBy(videoCode)\n//                HFileManager.deleteDownload(videoCode, count, file)\n                HFileManager.getDownloadVideoFolder(videoCode).deleteRecursively()\n                DatabaseRepo.HanimeDownload.delete(videoCode)\n                if (shouldDelete) {\n                    return@withContext Result.success()\n                }\n            }\n            val entity = DatabaseRepo.HanimeDownload.find(videoCode, quality) ?: kotlin.run {\n                // 如果不存在，创建新的 raf\n                createNewRaf(file)\n                DatabaseRepo.HanimeDownload.find(videoCode, quality)\n                    ?: return@withContext kotlin.run {\n                        Log.d(TAG, \"entity is null, create new raf failed\")\n                        showFailureNotification(context.getString(R.string.get_file_info_failed))\n                        mainScope.launch {\n                            showShortToast(\n                                context.getString(R.string.download_task_failed_s, hanimeName)\n                            )\n                        }\n                        Result.failure()\n                    }\n            }\n\n            if (entity.coverUri == null) {\n                updateCoverImage(entity)\n            }\n            if (isInWaitingQueue) {\n                DatabaseRepo.HanimeDownload.update(\n                    entity.copy(state = DownloadState.Queued)\n                )\n                return@withContext Result.success()\n            }\n\n            var downloadedLength = entity.downloadedLength\n            val needRange = entity.downloadedLength > 0\n            var raf: RandomAccessFile? = null\n            var response: Response? = null\n            var body: ResponseBody? = null\n            var bodyStream: InputStream? = null\n\n            var result: Result = Result.failure()\n            try {\n                raf = RandomAccessFile(file, \"rwd\")\n                val request = Request.Builder().url(downloadUrl)\n                    .also { if (needRange) it.header(\"Range\", \"bytes=${entity.downloadedLength}-\") }\n                    .get().build()\n                response = ServiceCreator.downloadClient.newCall(request).await()\n                raf.seek(entity.downloadedLength)\n                if ((needRange && response.code == 206) || (!needRange && response.isSuccessful)) {\n                    var delayTime = 0L\n                    body = response.body\n                    if (body != null) {\n                        bodyStream = body.byteStream()\n                        val buffer = ByteArray(DEFAULT_BUFFER_SIZE)\n                        var len: Int = bodyStream.read(buffer)\n                        while (len != -1) {\n                            raf.write(buffer, 0, len)\n                            downloadedLength += len\n                            if (System.currentTimeMillis() - delayTime > RESPONSE_INTERVAL) {\n                                val progress = downloadedLength * 100 / entity.length\n                                setProgress(workDataOf(PROGRESS to progress.toInt()))\n                                updateDownloadNotification(progress.toInt())\n                                DatabaseRepo.HanimeDownload.update(\n                                    entity.copy(\n                                        downloadedLength = downloadedLength,\n                                        // isDownloading = true,\n                                        state = DownloadState.Downloading\n                                    )\n                                )\n                                delayTime = System.currentTimeMillis()\n                            }\n                            len = bodyStream.read(buffer)\n                        }\n                    }\n                    showSuccessNotification()\n                    result = Result.success(\n                        workDataOf(DownloadState.STATE to DownloadState.Finished.mask)\n                    )\n                } else {\n                    Log.d(TAG, \"response failed: ${response.message}\")\n                    showFailureNotification(response.message)\n                    mainScope.launch {\n                        showShortToast(\n                            context.getString(R.string.download_task_failed_s, hanimeName)\n                        )\n                    }\n                    result = Result.failure(\n                        workDataOf(DownloadState.STATE to DownloadState.Failed.mask)\n                    )\n                }\n            } catch (e: Exception) {\n                result = if (e is CancellationException) {\n                    // cancellation exception block 是代表用户暂停\n                    cancelDownloadNotification()\n                    Result.success(\n                        workDataOf(DownloadState.STATE to DownloadState.Paused.mask)\n                    )\n                } else {\n                    showFailureNotification(e.localizedMessage)\n                    e.printStackTrace()\n                    mainScope.launch {\n                        showShortToast(e.localizedMessage)\n                    }\n                    Result.failure(\n                        workDataOf(DownloadState.STATE to DownloadState.Failed.mask)\n                    )\n                }\n            } finally {\n                // HanimeDownloadManager.notify(newEntity)\n\n                val state = DownloadState.from(\n                    result.outputData.getInt(DownloadState.STATE, DownloadState.Unknown.mask)\n                )\n                // 为什么要用 dbScope 包住？\n                // 使用 dbScope 是为了确保即使当前协程因任务取消而失效，\n                // “update”挂起函数仍然能够找到有效的协程作用域来更新数据库。\n                // 这也是一个历史遗留问题。\n                dbScope.launch {\n                    val newEntity = entity.copy(\n                        // isDownloading = false,\n                        state = state,\n                        downloadedLength = downloadedLength\n                    )\n                    DatabaseRepo.HanimeDownload.update(newEntity)\n                    Log.d(TAG, \"finally -> $newEntity\")\n                }\n                raf?.closeQuietly()\n                response?.closeQuietly()\n                body?.closeQuietly()\n                bodyStream?.closeQuietly()\n            }\n            return@withContext result\n        }\n    }\n\n    private fun CoroutineScope.updateCoverImage(entity: HanimeDownloadEntity) {\n        launch {\n            val imgRes = HImageMeower.execute(entity.coverUrl)\n            val file = HFileManager.getDownloadVideoCoverFile(videoCode, hanimeName)\n            val isSuccess = imgRes.drawable?.saveTo(file) == true\n            if (isSuccess) {\n                val coverUri = file.toUri().toString()\n                DatabaseRepo.HanimeDownload.update(entity.copy(coverUri = coverUri))\n                // 不得不用 var，要不然有点难搞\n                entity.coverUri = coverUri\n            }\n        }\n    }\n\n    private fun createDownloadNotification(progress: Int = 0): Notification {\n        return NotificationCompat.Builder(context, DOWNLOAD_NOTIFICATION_CHANNEL)\n            .setSmallIcon(R.mipmap.ic_launcher)\n            .setOngoing(true)\n            .setOnlyAlertOnce(true)\n            .setContentTitle(context.getString(R.string.downloading_s, hanimeName))\n            .setPriority(NotificationCompat.PRIORITY_HIGH)\n            .setContentText(\"$progress%\")\n            .setProgress(100, progress, false)\n            .build()\n    }\n\n    private fun cancelDownloadNotification() {\n        notificationManager.cancel(downloadId)\n    }\n\n    @SuppressLint(\"MissingPermission\")\n    private fun updateDownloadNotification(progress: Int) {\n        notificationManager.notify(downloadId, createDownloadNotification(progress))\n    }\n\n    private fun createForegroundInfo(progress: Int = 0): ForegroundInfo {\n        val notification = createDownloadNotification(progress)\n        return ForegroundInfo(\n            downloadId, notification,\n            // #issue-34: 這裡的參數是為了讓 Android 14 以上的系統可以正常顯示前景通知\n            if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.Q) {\n                ServiceInfo.FOREGROUND_SERVICE_TYPE_DATA_SYNC\n            } else 0\n        )\n    }\n\n    @SuppressLint(\"MissingPermission\")\n    private fun showSuccessNotification() {\n        notificationManager.notify(\n            downloadId, NotificationCompat.Builder(context, DOWNLOAD_NOTIFICATION_CHANNEL)\n                .setSmallIcon(R.drawable.ic_baseline_check_circle_24)\n                .setContentTitle(context.getString(R.string.download_task_completed))\n                .setPriority(NotificationCompat.PRIORITY_HIGH)\n                .setAutoCancel(true)\n                .setContentText(context.getString(R.string.download_completed_s, hanimeName))\n                .build()\n        )\n    }\n\n    @SuppressLint(\"MissingPermission\")\n    private fun showFileExistsFailureNotification(fileName: String) {\n        notificationManager.notify(\n            downloadId, NotificationCompat.Builder(context, DOWNLOAD_NOTIFICATION_CHANNEL)\n                .setSmallIcon(R.drawable.ic_baseline_cancel_24)\n                .setContentTitle(context.getString(R.string.this_data_exists))\n                .setPriority(NotificationCompat.PRIORITY_HIGH)\n                .setContentText(context.getString(R.string.download_failed_s_exists, fileName))\n                .build()\n        )\n    }\n\n    @SuppressLint(\"MissingPermission\")\n    private fun showFailureNotification(errMsg: String? = null) {\n        notificationManager.notify(\n            downloadId, NotificationCompat.Builder(context, DOWNLOAD_NOTIFICATION_CHANNEL)\n                .setSmallIcon(R.drawable.ic_baseline_cancel_24)\n                .setContentTitle(context.getString(R.string.download_task_failed))\n                .setPriority(NotificationCompat.PRIORITY_HIGH)\n                .setAutoCancel(true)\n                .setContentText(\n                    context.getString(\n                        R.string.download_task_failed_s_reason_s,\n                        hanimeName, errMsg ?: context.getString(R.string.unknown_download_error)\n                    )\n                )\n                .build()\n        )\n    }\n}"
  },
  {
    "path": "app/src/main/java/com/yenaly/han1meviewer/worker/WorkerMixin.kt",
    "content": "package com.yenaly.han1meviewer.worker\n\nimport androidx.work.ListenableWorker\nimport com.yenaly.yenaly_libs.utils.unsafeLazy\n\n/**\n * @project Han1meViewer\n * @author Yenaly Liew\n * @time 2024/03/22 022 21:33\n */\n@JvmDefaultWithoutCompatibility\ninterface WorkerMixin {\n    @Suppress(\"UNCHECKED_CAST\", \"SameParameterValue\")\n    fun <T : Any> ListenableWorker.inputData(key: String, def: T): Lazy<T> = unsafeLazy {\n        when (def) {\n            is String -> (inputData.getString(key) ?: def) as T\n            is Int -> inputData.getInt(key, def) as T\n            is Long -> inputData.getLong(key, def) as T\n            is Boolean -> inputData.getBoolean(key, def) as T\n            is Float -> inputData.getFloat(key, def) as T\n            else -> throw IllegalArgumentException(\"Unsupported type\")\n        }\n    }\n}"
  },
  {
    "path": "app/src/main/res/anim/fade_in.xml",
    "content": "<?xml version=\"1.0\" encoding=\"utf-8\"?>\n<alpha xmlns:android=\"http://schemas.android.com/apk/res/android\"\n    android:interpolator=\"@android:interpolator/fast_out_slow_in\"\n    android:fromAlpha=\"0.0\"\n    android:toAlpha=\"1.0\"\n    android:duration=\"@android:integer/config_shortAnimTime\" />"
  },
  {
    "path": "app/src/main/res/anim/fade_out.xml",
    "content": "<?xml version=\"1.0\" encoding=\"utf-8\"?>\n<alpha xmlns:android=\"http://schemas.android.com/apk/res/android\"\n    android:interpolator=\"@android:interpolator/fast_out_slow_in\"\n    android:fromAlpha=\"1.0\"\n    android:toAlpha=\"0.0\"\n    android:duration=\"@android:integer/config_shortAnimTime\" />"
  },
  {
    "path": "app/src/main/res/anim/slide_in_from_bottom.xml",
    "content": "<?xml version=\"1.0\" encoding=\"utf-8\"?>\n<set xmlns:android=\"http://schemas.android.com/apk/res/android\">\n    <translate\n        android:duration=\"@android:integer/config_shortAnimTime\"\n        android:fromYDelta=\"100%p\"\n        android:interpolator=\"@android:interpolator/fast_out_slow_in\"\n        android:toYDelta=\"0%p\" />\n    <alpha\n        android:duration=\"@android:integer/config_shortAnimTime\"\n        android:fromAlpha=\"0.0\"\n        android:interpolator=\"@android:interpolator/fast_out_slow_in\"\n        android:toAlpha=\"1.0\" />\n</set>"
  },
  {
    "path": "app/src/main/res/anim/slide_out_to_bottom.xml",
    "content": "<?xml version=\"1.0\" encoding=\"utf-8\"?>\n<set xmlns:android=\"http://schemas.android.com/apk/res/android\">\n    <translate\n        android:duration=\"@android:integer/config_shortAnimTime\"\n        android:fromYDelta=\"0%p\"\n        android:interpolator=\"@android:interpolator/fast_out_slow_in\"\n        android:toYDelta=\"100%p\" />\n    <alpha\n        android:duration=\"@android:integer/config_shortAnimTime\"\n        android:fromAlpha=\"1.0\"\n        android:interpolator=\"@android:interpolator/fast_out_slow_in\"\n        android:toAlpha=\"0.0\" />\n</set>"
  },
  {
    "path": "app/src/main/res/drawable/baseline_add_24.xml",
    "content": "<vector android:height=\"24dp\" android:tint=\"#FFFFFF\"\n    android:viewportHeight=\"24\" android:viewportWidth=\"24\"\n    android:width=\"24dp\" xmlns:android=\"http://schemas.android.com/apk/res/android\">\n    <path android:fillColor=\"@android:color/white\" android:pathData=\"M19,13h-6v6h-2v-6H5v-2h6V5h2v6h6v2z\"/>\n</vector>\n"
  },
  {
    "path": "app/src/main/res/drawable/baseline_add_link_24.xml",
    "content": "<vector xmlns:android=\"http://schemas.android.com/apk/res/android\" android:height=\"24dp\" android:tint=\"#FFFFFF\" android:viewportHeight=\"24\" android:viewportWidth=\"24\" android:width=\"24dp\">\n      \n    <path android:fillColor=\"@android:color/white\" android:pathData=\"M8,11h8v2L8,13zM20.1,12L22,12c0,-2.76 -2.24,-5 -5,-5h-4v1.9h4c1.71,0 3.1,1.39 3.1,3.1zM3.9,12c0,-1.71 1.39,-3.1 3.1,-3.1h4L11,7L7,7c-2.76,0 -5,2.24 -5,5s2.24,5 5,5h4v-1.9L7,15.1c-1.71,0 -3.1,-1.39 -3.1,-3.1zM19,12h-2v3h-3v2h3v3h2v-3h3v-2h-3z\"/>\n    \n</vector>\n"
  },
  {
    "path": "app/src/main/res/drawable/baseline_advanced_search_24.xml",
    "content": "<vector xmlns:android=\"http://schemas.android.com/apk/res/android\" android:height=\"24dp\" android:viewportHeight=\"1024\" android:viewportWidth=\"1024\" android:width=\"24dp\">\n      \n    <path android:fillColor=\"#707070\" android:pathData=\"M104.1,1.9h815.5c39.4,0 74.9,23 91.3,59.2 16.4,36.1 9.2,77.5 -17.8,107.1l-3.9,4L683,417.2a21,21 0,0 0,-7.2 15.8v484.9c0.6,55.9 -44,99.9 -99.3,99.9a101.6,101.6 0,0 1,-47.9 -11.9l-130.8,-72.9a98.3,98.3 0,0 1,-51.9 -87.4L346,430.3a20.6,20.6 0,0 0,-7.9 -15.8L35.9,172.1l-3.3,-4A96.4,96.4 0,0 1,13.5 61.1,96.8 96.8,0 0,1 104.1,1.9zM75.5,81c-3.6,6.9 -3.1,15.2 1.4,21.6L385.6,349.4c24.2,19.6 38.4,48.5 38.4,79.5v426.1c0,7.4 3.4,14.8 10.1,18.2l134.2,73.4c6.1,3.4 14.2,3.4 20.9,0a21.2,21.2 0,0 0,10.2 -18.2L599.3,431.6c0,-31 13.5,-60 37.7,-79.5l309.4,-249.4c4.7,-6.3 5.5,-14.6 2.1,-21.6a20.3,20.3 0,0 0,-18.3 -11.5L93.7,69.6a19.1,19.1 0,0 0,-18.2 11.5zM979.4,743.1c21.7,0 39.4,17.8 39.4,39.4a39.1,39.1 0,0 1,-39.4 39.5h-224.1a39.6,39.6 0,0 1,-39.4 -39.5c0,-21.7 17.8,-39.4 39.4,-39.4h224.1zM979.4,595.2c21.7,0 39.4,17.8 39.4,39.5a38.7,38.7 0,0 1,-39.4 39.4h-224.1a39.6,39.6 0,0 1,-39.4 -39.4c0,-21.7 17.8,-39.5 39.4,-39.5h224.1zM979.4,446.7c21.7,0 39.4,17.8 39.4,39.5a38.7,38.7 0,0 1,-39.4 39.4h-224.1a39.6,39.6 0,0 1,-39.4 -39.4c0,-21.7 17.8,-39.5 39.4,-39.5h224.1z\"/>\n    \n</vector>\n"
  },
  {
    "path": "app/src/main/res/drawable/baseline_arrow_forward_ios_24.xml",
    "content": "<vector android:autoMirrored=\"true\" android:height=\"24dp\"\n    android:tint=\"#FFFFFF\" android:viewportHeight=\"24\"\n    android:viewportWidth=\"24\" android:width=\"24dp\" xmlns:android=\"http://schemas.android.com/apk/res/android\">\n    <path android:fillColor=\"@android:color/white\" android:pathData=\"M6.23,20.23l1.77,1.77l10,-10l-10,-10l-1.77,1.77l8.23,8.23z\"/>\n</vector>\n"
  },
  {
    "path": "app/src/main/res/drawable/baseline_bug_report_24.xml",
    "content": "<vector android:height=\"24dp\" android:tint=\"#FFFFFF\"\n    android:viewportHeight=\"24\" android:viewportWidth=\"24\"\n    android:width=\"24dp\" xmlns:android=\"http://schemas.android.com/apk/res/android\">\n    <path android:fillColor=\"@android:color/white\" 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</vector>\n"
  },
  {
    "path": "app/src/main/res/drawable/baseline_data_usage_24.xml",
    "content": "<vector xmlns:android=\"http://schemas.android.com/apk/res/android\" android:height=\"24dp\" android:tint=\"#FFFFFF\" android:viewportHeight=\"24\" android:viewportWidth=\"24\" android:width=\"24dp\">\n      \n    <path android:fillColor=\"@android:color/white\" android:pathData=\"M13,2.05v3.03c3.39,0.49 6,3.39 6,6.92 0,0.9 -0.18,1.75 -0.48,2.54l2.6,1.53c0.56,-1.24 0.88,-2.62 0.88,-4.07 0,-5.18 -3.95,-9.45 -9,-9.95zM12,19c-3.87,0 -7,-3.13 -7,-7 0,-3.53 2.61,-6.43 6,-6.92V2.05c-5.06,0.5 -9,4.76 -9,9.95 0,5.52 4.47,10 9.99,10 3.31,0 6.24,-1.61 8.06,-4.09l-2.6,-1.53C16.17,17.98 14.21,19 12,19z\"/>\n    \n</vector>\n"
  },
  {
    "path": "app/src/main/res/drawable/baseline_dns_24.xml",
    "content": "<vector android:height=\"24dp\" android:viewportHeight=\"1024\"\n    android:viewportWidth=\"1024\" android:width=\"24dp\" xmlns:android=\"http://schemas.android.com/apk/res/android\">\n    <path android:fillColor=\"#FFFFFFFF\" android:pathData=\"M181.3,640h123.7c25.6,0 46.9,-23.5 46.9,-51.2v-153.6c0,-27.7 -21.3,-51.2 -46.9,-51.2L160,384v253.9l21.3,2.1zM181.3,407.5h123.7c14.9,0 25.6,12.8 25.6,29.9v153.6c0,17.1 -12.8,29.9 -25.6,29.9L181.3,620.8v-213.3zM529.1,601.6c10.7,27.7 29.9,38.4 61.9,38.4L618.7,640L618.7,386.1h-21.3L597.3,618.7h-6.4c-23.5,0 -34.1,-6.4 -40.5,-25.6l-57.6,-162.1c-4.3,-10.7 -17.1,-46.9 -59.7,-46.9L405.3,384v253.9h21.3L426.7,407.5h6.4c19.2,0 29.9,8.5 38.4,32l57.6,162.1M718.9,512h100.3c14.9,0 25.6,12.8 25.6,29.9v49.1c0,17.1 -10.7,29.9 -25.6,29.9h-142.9v21.3h142.9c25.6,0 46.9,-23.5 46.9,-51.2v-49.1c0,-27.7 -21.3,-51.2 -46.9,-51.2h-100.3c-14.9,0 -25.6,-12.8 -25.6,-29.9v-27.7c0,-17.1 10.7,-29.9 25.6,-29.9h142.9v-21.3h-142.9c-25.6,0 -46.9,23.5 -46.9,51.2v27.7c0,27.7 21.3,51.2 46.9,51.2z\"/>\n    <path android:fillColor=\"#FFFFFFFF\" android:pathData=\"M512,42.7C253.9,42.7 42.7,251.7 42.7,512s211.2,469.3 469.3,469.3c260.3,0 469.3,-209.1 469.3,-469.3S772.3,42.7 512,42.7zM512,949.3c-142.9,0 -268.8,-72.5 -347.7,-183.5L725.3,765.9l-74.7,53.3c-2.1,0 -2.1,2.1 0,4.3l4.3,4.3c4.3,6.4 10.7,6.4 17.1,2.1l91.7,-66.1c2.1,-2.1 4.3,-4.3 4.3,-8.5v-4.3c0,-4.3 -4.3,-8.5 -8.5,-8.5L151.5,742.4C108.8,676.3 85.3,597.3 85.3,512 85.3,270.9 277.3,74.7 512,74.7c151.5,0 285.9,81.1 360.5,204.8L298.7,279.5l74.7,-53.3c2.1,0 2.1,-2.1 0,-4.3l-4.3,-4.3c-4.3,-6.4 -10.7,-6.4 -17.1,-2.1l-91.7,66.1c-2.1,2.1 -4.3,4.3 -4.3,8.5v4.3c0,4.3 4.3,8.5 8.5,8.5h620.8C919.5,362.7 938.7,435.2 938.7,512c0,241.1 -192,437.3 -426.7,437.3z\"/>\n</vector>\n"
  },
  {
    "path": "app/src/main/res/drawable/baseline_edit_24.xml",
    "content": "<vector android:height=\"24dp\" android:tint=\"#FFFFFF\"\n    android:viewportHeight=\"24\" android:viewportWidth=\"24\"\n    android:width=\"24dp\" xmlns:android=\"http://schemas.android.com/apk/res/android\">\n    <path android:fillColor=\"@android:color/white\" android:pathData=\"M3,17.25V21h3.75L17.81,9.94l-3.75,-3.75L3,17.25zM20.71,7.04c0.39,-0.39 0.39,-1.02 0,-1.41l-2.34,-2.34c-0.39,-0.39 -1.02,-0.39 -1.41,0l-1.83,1.83 3.75,3.75 1.83,-1.83z\"/>\n</vector>\n"
  },
  {
    "path": "app/src/main/res/drawable/baseline_error_outline_24.xml",
    "content": "<vector xmlns:android=\"http://schemas.android.com/apk/res/android\" android:height=\"24dp\" android:tint=\"#000000\" android:viewportHeight=\"24\" android:viewportWidth=\"24\" android:width=\"24dp\">\n      \n    <path android:fillColor=\"@android:color/white\" android:pathData=\"M11,15h2v2h-2zM11,7h2v6h-2zM11.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,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    \n</vector>\n"
  },
  {
    "path": "app/src/main/res/drawable/baseline_format_list_bulleted_24.xml",
    "content": "<vector android:autoMirrored=\"true\" android:height=\"24dp\"\n    android:tint=\"#FFFFFF\" android:viewportHeight=\"24\"\n    android:viewportWidth=\"24\" android:width=\"24dp\" xmlns:android=\"http://schemas.android.com/apk/res/android\">\n    <path android:fillColor=\"@android:color/white\" android:pathData=\"M4,10.5c-0.83,0 -1.5,0.67 -1.5,1.5s0.67,1.5 1.5,1.5 1.5,-0.67 1.5,-1.5 -0.67,-1.5 -1.5,-1.5zM4,4.5c-0.83,0 -1.5,0.67 -1.5,1.5S3.17,7.5 4,7.5 5.5,6.83 5.5,6 4.83,4.5 4,4.5zM4,16.5c-0.83,0 -1.5,0.68 -1.5,1.5s0.68,1.5 1.5,1.5 1.5,-0.68 1.5,-1.5 -0.67,-1.5 -1.5,-1.5zM7,19h14v-2L7,17v2zM7,13h14v-2L7,11v2zM7,5v2h14L21,5L7,5z\"/>\n</vector>\n"
  },
  {
    "path": "app/src/main/res/drawable/baseline_forum_24.xml",
    "content": "<vector android:height=\"24dp\" android:tint=\"#FFFFFF\"\n    android:viewportHeight=\"24\" android:viewportWidth=\"24\"\n    android:width=\"24dp\" xmlns:android=\"http://schemas.android.com/apk/res/android\">\n    <path android:fillColor=\"@android:color/white\" android:pathData=\"M21,6h-2v9L6,15v2c0,0.55 0.45,1 1,1h11l4,4L22,7c0,-0.55 -0.45,-1 -1,-1zM17,12L17,3c0,-0.55 -0.45,-1 -1,-1L3,2c-0.55,0 -1,0.45 -1,1v14l4,-4h10c0.55,0 1,-0.45 1,-1z\"/>\n</vector>\n"
  },
  {
    "path": "app/src/main/res/drawable/baseline_h_24.xml",
    "content": "<vector android:height=\"24dp\" android:viewportHeight=\"1024\"\n    android:viewportWidth=\"1024\" android:width=\"24dp\" xmlns:android=\"http://schemas.android.com/apk/res/android\">\n    <path android:fillColor=\"#FF0000\" android:pathData=\"M235.5,871.7v-740h98v304h385v-304h98v740h-98v-349h-385v349h-98z\"/>\n</vector>\n"
  },
  {
    "path": "app/src/main/res/drawable/baseline_keyboard_arrow_down_24.xml",
    "content": "<vector android:height=\"24dp\" android:tint=\"#FFFFFF\"\n    android:viewportHeight=\"24\" android:viewportWidth=\"24\"\n    android:width=\"24dp\" xmlns:android=\"http://schemas.android.com/apk/res/android\">\n    <path android:fillColor=\"@android:color/white\" android:pathData=\"M7.41,8.59L12,13.17l4.59,-4.58L18,10l-6,6 -6,-6 1.41,-1.41z\"/>\n</vector>\n"
  },
  {
    "path": "app/src/main/res/drawable/baseline_keyboard_arrow_up_24.xml",
    "content": "<vector android:height=\"24dp\" android:tint=\"#FFFFFF\"\n    android:viewportHeight=\"24\" android:viewportWidth=\"24\"\n    android:width=\"24dp\" xmlns:android=\"http://schemas.android.com/apk/res/android\">\n    <path android:fillColor=\"@android:color/white\" android:pathData=\"M7.41,15.41L12,10.83l4.59,4.58L18,14l-6,-6 -6,6z\"/>\n</vector>\n"
  },
  {
    "path": "app/src/main/res/drawable/baseline_link_24.xml",
    "content": "<vector android:height=\"24dp\" android:tint=\"#FFFFFF\"\n    android:viewportHeight=\"24\" android:viewportWidth=\"24\"\n    android:width=\"24dp\" xmlns:android=\"http://schemas.android.com/apk/res/android\">\n    <path android:fillColor=\"@android:color/white\" android:pathData=\"M3.9,12c0,-1.71 1.39,-3.1 3.1,-3.1h4L11,7L7,7c-2.76,0 -5,2.24 -5,5s2.24,5 5,5h4v-1.9L7,15.1c-1.71,0 -3.1,-1.39 -3.1,-3.1zM8,13h8v-2L8,11v2zM17,7h-4v1.9h4c1.71,0 3.1,1.39 3.1,3.1s-1.39,3.1 -3.1,3.1h-4L13,17h4c2.76,0 5,-2.24 5,-5s-2.24,-5 -5,-5z\"/>\n</vector>\n"
  },
  {
    "path": "app/src/main/res/drawable/baseline_more_horiz_24.xml",
    "content": "<vector android:height=\"24dp\" android:tint=\"#FFFFFF\"\n    android:viewportHeight=\"24\" android:viewportWidth=\"24\"\n    android:width=\"24dp\" xmlns:android=\"http://schemas.android.com/apk/res/android\">\n    <path android:fillColor=\"@android:color/white\" android:pathData=\"M6,10c-1.1,0 -2,0.9 -2,2s0.9,2 2,2 2,-0.9 2,-2 -0.9,-2 -2,-2zM18,10c-1.1,0 -2,0.9 -2,2s0.9,2 2,2 2,-0.9 2,-2 -0.9,-2 -2,-2zM12,10c-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/baseline_refresh_24.xml",
    "content": "<vector android:height=\"24dp\" android:tint=\"#FFFFFF\"\n    android:viewportHeight=\"24\" android:viewportWidth=\"24\"\n    android:width=\"24dp\" xmlns:android=\"http://schemas.android.com/apk/res/android\">\n    <path android:fillColor=\"@android:color/white\" android:pathData=\"M17.65,6.35C16.2,4.9 14.21,4 12,4c-4.42,0 -7.99,3.58 -7.99,8s3.57,8 7.99,8c3.73,0 6.84,-2.55 7.73,-6h-2.08c-0.82,2.33 -3.04,4 -5.65,4 -3.31,0 -6,-2.69 -6,-6s2.69,-6 6,-6c1.66,0 3.14,0.69 4.22,1.78L13,11h7V4l-2.35,2.35z\"/>\n</vector>\n"
  },
  {
    "path": "app/src/main/res/drawable/baseline_remove_circle_24.xml",
    "content": "<vector xmlns:android=\"http://schemas.android.com/apk/res/android\" android:height=\"24dp\" android:tint=\"#FF0000\" android:viewportHeight=\"24\" android:viewportWidth=\"24\" android:width=\"24dp\">\n      \n    <path android:fillColor=\"@android:color/white\" android:pathData=\"M12,2C6.48,2 2,6.48 2,12s4.48,10 10,10 10,-4.48 10,-10S17.52,2 12,2zM17,13L7,13v-2h10v2z\"/>\n    \n</vector>\n"
  },
  {
    "path": "app/src/main/res/drawable/baseline_simp_to_trad_24.xml",
    "content": "<vector android:height=\"24dp\" android:viewportHeight=\"1024\"\n    android:viewportWidth=\"1024\" android:width=\"24dp\" xmlns:android=\"http://schemas.android.com/apk/res/android\">\n    <path android:fillColor=\"#FFFFFFFF\" android:pathData=\"M231.1,644.7v88.4c0,46.3 35.7,84.8 81.8,88.2l6.6,0.2h132.7V910H319.6c-97.7,0 -176.9,-79.2 -176.9,-176.9v-88.4h88.4zM850.2,290.9v88.4h88.4v-88.4c0,-97.7 -79.2,-176.9 -176.9,-176.9H629.1v88.4h132.7c48.8,0 88.4,39.6 88.4,88.5zM82.2,265.6h63.2v265.8H82.2zM144.8,181.9l-44.1,37.6c11.4,10.4 26.6,24.9 45.6,43.6 5,4.7 8.7,8.2 11,10.5l45.6,-40.1c-19.3,-18.7 -38.7,-35.9 -58.1,-51.6z\"/>\n    <path android:fillColor=\"#FFFFFFFF\" android:pathData=\"M401.6,61.6L336.9,56c-15.4,42.1 -37.3,78.7 -65.7,109.8 22.4,13 39.8,23.9 52.1,32.6L213.5,198.4L213.5,250h221.6v189c1,20.7 -8.9,30.6 -29.6,29.6L393,468.6L393,278.2L186.5,278.2v200.1L355,478.3c3.7,12.4 7.9,29.1 12.5,50.1h40.6c0.3,0.3 8.2,0 23.6,-1 45.8,-2.3 68.2,-26.7 67.2,-73.2L498.9,198.5L325.4,198.5c12,-15.4 24.6,-34.3 37.6,-56.7h20.6c9,14.7 19.6,30.9 31.6,48.6l57.2,-10.5 -27.6,-38.1L518,141.8L518,91.7L389,91.7c4.4,-9.7 8.6,-19.8 12.6,-30.1zM334.4,432.6h-89.3v-32.1h89.3v32.1zM334.4,355.4h-89.3v-31.6h89.3v31.6z\"/>\n    <path android:fillColor=\"#FFFFFFFF\" android:pathData=\"M147.4,141.8h18.1c6,10 14.5,23.7 25.6,41.1 2.3,3.7 4,6.4 5,8l57.2,-10.5c-4.7,-6.3 -13.7,-19.2 -27.1,-38.6h61.2L287.4,91.1h-110l14,-30.1 -64.7,-4.5C108.3,99 81.7,134.7 47,163.8c2.3,2.7 6,7.2 11,13.5 13,16.4 21.7,27.4 26.1,33.1 25.8,-22.6 46.9,-45.5 63.3,-68.6zM923.8,648.1c19.4,-21.7 33.9,-50.5 43.6,-86.2h24.1v-46.1L872.6,515.8c0.7,-1.7 1.7,-4.2 3,-7.5 5,-11.4 8.7,-20.1 11,-26.1l-56.2,-5.5c-15,41.5 -35.6,76.2 -61.7,104.3 2,2.7 5,7 9,13 5.7,8.4 9.7,14.4 12,18.1h-30.6l4,-56.7L591.8,555.4c5,-5.7 10,-11.7 15,-18.1h173v-36.6L631.9,500.7l12,-20.6 -56.2,-5.5c-18.1,33.1 -41,59.8 -68.7,80.2 8.4,7 20.9,18.9 37.6,35.6l8.5,-7.5 -6,29.1L521,612v40.6h30.1l-11,56.7L673,709.3c-12.4,5.4 -28.6,11.9 -48.6,19.6 -14.4,5.7 -32.6,10.2 -54.7,13.5l14.5,38.6c16.7,-2 31.6,-3.2 44.6,-3.5l69.7,-4c-33.1,11.4 -63.5,21.1 -91.3,29.1 -22.7,6.7 -43.1,11 -61.2,13l15.5,42.6c17.4,-2 38.6,-3.5 63.7,-4.5l110.3,-4.5v24.6c1.3,17.4 -7.2,25.4 -25.6,24.1h-17.5c2,-1 4,-1.7 6,-2l-27.6,-38.6c-51.5,15.7 -101.6,29.1 -150.4,40.1l22.1,45.6c50.5,-14.4 91.3,-26.6 122.3,-36.6l11,42.1h37.1c9,-0.3 16.9,-0.7 23.6,-1 42.4,-0.3 62.3,-20.6 59.7,-60.7v-39.1c35.1,-1 72.2,-1.8 111.3,-2.5l28.6,21.1 36.6,-32.6c-39.8,-27.1 -75.2,-50.3 -106.3,-69.7L828,788.5l25.1,17.5 -155.4,6c77.9,-23.7 150.1,-46.8 216.6,-69.2l-39.6,-35.1c-27.1,11 -53,21.2 -77.7,30.6l-117.8,4c16.4,-5.7 44.6,-16 84.7,-31.1 -1.7,0.7 -0.2,0.2 4.5,-1.5l-1,-0.5h7v-31.6h-19.6l2,-25.1h35.6v-37.1l6.5,10.5c6,-6.7 11.7,-13.2 17,-19.6 8.4,17.4 17.4,31.6 27.1,42.6 -18.4,9.7 -41.6,18.6 -69.7,26.6 8.4,13.4 16.7,27.9 25.1,43.6 32.8,-10 60.5,-22.1 83.2,-36.1 21.1,13.4 51,26.4 89.8,39.1 4,-9.7 11.2,-24.4 21.6,-44.1 1,-2.3 1.8,-4 2.5,-5 -29.4,-7.1 -53.3,-15.5 -71.7,-24.9zM613.4,586.5h17.5l20.6,31.6h-44.1l6,-31.6zM595.8,677.7l6,-31.1h23.1l19.6,31.1h-48.7zM706.2,677.7L648,677.7l35.6,-13.5 -12,-17.5h37.1l-2.5,31zM711.2,618.1L656,618.1l34.6,-13.5 -13,-18.1h36.6l-3,31.6zM883.2,618.6c-13.4,-14.4 -24.6,-33.3 -33.6,-56.7h64.7c-7.8,23.7 -18.1,42.6 -31.1,56.7z\"/>\n    <path android:fillColor=\"#FFFFFFFF\" android:pathData=\"M817,892.8c69.5,23.1 119,40.1 148.4,51.1l25.6,-43.6c-40.5,-13.7 -89.8,-29.2 -147.9,-46.6L817,892.8z\"/>\n</vector>\n"
  },
  {
    "path": "app/src/main/res/drawable/baseline_sort_24.xml",
    "content": "<vector android:autoMirrored=\"true\" android:height=\"24dp\"\n    android:tint=\"#FFFFFF\" android:viewportHeight=\"24\"\n    android:viewportWidth=\"24\" android:width=\"24dp\" xmlns:android=\"http://schemas.android.com/apk/res/android\">\n    <path android:fillColor=\"@android:color/white\" android:pathData=\"M3,18h6v-2L3,16v2zM3,6v2h18L21,6L3,6zM3,13h12v-2L3,11v2z\"/>\n</vector>\n"
  },
  {
    "path": "app/src/main/res/drawable/gradient_black80_transparent_270.xml",
    "content": "<?xml version=\"1.0\" encoding=\"utf-8\"?>\n<shape xmlns:android=\"http://schemas.android.com/apk/res/android\">\n\n    <gradient\n        android:angle=\"270\"\n        android:endColor=\"@android:color/transparent\"\n        android:startColor=\"@color/per80_transparent_black\" />\n\n</shape>"
  },
  {
    "path": "app/src/main/res/drawable/gradient_black_transparent_90.xml",
    "content": "<?xml version=\"1.0\" encoding=\"utf-8\"?>\n<shape xmlns:android=\"http://schemas.android.com/apk/res/android\">\n\n    <gradient\n        android:angle=\"90\"\n        android:endColor=\"@android:color/transparent\"\n        android:startColor=\"@color/black\" />\n\n</shape>"
  },
  {
    "path": "app/src/main/res/drawable/ic_baseline_access_time_24.xml",
    "content": "<vector android:height=\"24dp\" android:tint=\"#FFFFFFFF\"\n    android:viewportHeight=\"24\" android:viewportWidth=\"24\"\n    android:width=\"24dp\" xmlns:android=\"http://schemas.android.com/apk/res/android\">\n    <path android:fillColor=\"@android:color/white\" android: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,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    <path android:fillColor=\"@android:color/white\" android:pathData=\"M12.5,7H11v6l5.25,3.15 0.75,-1.23 -4.5,-2.67z\"/>\n</vector>\n"
  },
  {
    "path": "app/src/main/res/drawable/ic_baseline_arrow_back_24.xml",
    "content": "<vector android:autoMirrored=\"true\" android:height=\"24dp\"\n    android:tint=\"#FFFFFF\" android:viewportHeight=\"24\"\n    android:viewportWidth=\"24\" android:width=\"24dp\" xmlns:android=\"http://schemas.android.com/apk/res/android\">\n    <path android:fillColor=\"@android:color/white\" android:pathData=\"M20,11H7.83l5.59,-5.59L12,4l-8,8 8,8 1.41,-1.41L7.83,13H20v-2z\"/>\n</vector>\n"
  },
  {
    "path": "app/src/main/res/drawable/ic_baseline_arrow_forward_24.xml",
    "content": "<vector android:autoMirrored=\"true\" android:height=\"24dp\"\n    android:tint=\"#FFFFFFFF\" android:viewportHeight=\"24\"\n    android:viewportWidth=\"24\" android:width=\"24dp\" xmlns:android=\"http://schemas.android.com/apk/res/android\">\n    <path android:fillColor=\"@android:color/white\" android: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_baseline_cancel_24.xml",
    "content": "<vector android:height=\"24dp\" android:tint=\"#000000\"\n    android:viewportHeight=\"24\" android:viewportWidth=\"24\"\n    android:width=\"24dp\" xmlns:android=\"http://schemas.android.com/apk/res/android\">\n    <path android:fillColor=\"@android:color/white\" android:pathData=\"M12,2C6.47,2 2,6.47 2,12s4.47,10 10,10 10,-4.47 10,-10S17.53,2 12,2zM17,15.59L15.59,17 12,13.41 8.41,17 7,15.59 10.59,12 7,8.41 8.41,7 12,10.59 15.59,7 17,8.41 13.41,12 17,15.59z\"/>\n</vector>\n"
  },
  {
    "path": "app/src/main/res/drawable/ic_baseline_check_circle_24.xml",
    "content": "<vector android:height=\"24dp\" android:tint=\"#000000\"\n    android:viewportHeight=\"24\" android:viewportWidth=\"24\"\n    android:width=\"24dp\" xmlns:android=\"http://schemas.android.com/apk/res/android\">\n    <path android:fillColor=\"@android:color/white\" android:pathData=\"M12,2C6.48,2 2,6.48 2,12s4.48,10 10,10 10,-4.48 10,-10S17.52,2 12,2zM10,17l-5,-5 1.41,-1.41L10,14.17l7.59,-7.59L19,8l-9,9z\"/>\n</vector>\n"
  },
  {
    "path": "app/src/main/res/drawable/ic_baseline_checklist_24.xml",
    "content": "<vector android:height=\"24dp\" android:tint=\"#FFFFFFFF\"\n    android:viewportHeight=\"24\" android:viewportWidth=\"24\"\n    android:width=\"24dp\" xmlns:android=\"http://schemas.android.com/apk/res/android\">\n    <path android:fillColor=\"@android:color/white\" android:pathData=\"M22,7h-9v2h9V7zM22,15h-9v2h9V15zM5.54,11L2,7.46l1.41,-1.41l2.12,2.12l4.24,-4.24l1.41,1.41L5.54,11zM5.54,19L2,15.46l1.41,-1.41l2.12,2.12l4.24,-4.24l1.41,1.41L5.54,19z\"/>\n</vector>\n"
  },
  {
    "path": "app/src/main/res/drawable/ic_baseline_clear_all_24.xml",
    "content": "<vector android:height=\"24dp\" android:tint=\"#FFFFFF\"\n    android:viewportHeight=\"24\" android:viewportWidth=\"24\"\n    android:width=\"24dp\" xmlns:android=\"http://schemas.android.com/apk/res/android\">\n    <path android:fillColor=\"@android:color/white\" android:pathData=\"M5,13h14v-2L5,11v2zM3,17h14v-2L3,15v2zM7,7v2h14L21,7L7,7z\"/>\n</vector>\n"
  },
  {
    "path": "app/src/main/res/drawable/ic_baseline_close_24.xml",
    "content": "<vector android:height=\"24dp\" android:tint=\"#000000\"\n    android:viewportHeight=\"24\" android:viewportWidth=\"24\"\n    android:width=\"24dp\" xmlns:android=\"http://schemas.android.com/apk/res/android\">\n    <path android:fillColor=\"@android:color/white\" android:pathData=\"M19,6.41L17.59,5 12,10.59 6.41,5 5,6.41 10.59,12 5,17.59 6.41,19 12,13.41 17.59,19 19,17.59 13.41,12z\"/>\n</vector>\n"
  },
  {
    "path": "app/src/main/res/drawable/ic_baseline_comment_24.xml",
    "content": "<vector android:autoMirrored=\"true\" android:height=\"24dp\"\n    android:tint=\"#FFFFFF\" android:viewportHeight=\"24\"\n    android:viewportWidth=\"24\" android:width=\"24dp\" xmlns:android=\"http://schemas.android.com/apk/res/android\">\n    <path android:fillColor=\"@android:color/white\" android:pathData=\"M21.99,4c0,-1.1 -0.89,-2 -1.99,-2L4,2c-1.1,0 -2,0.9 -2,2v12c0,1.1 0.9,2 2,2h14l4,4 -0.01,-18zM18,14L6,14v-2h12v2zM18,11L6,11L6,9h12v2zM18,8L6,8L6,6h12v2z\"/>\n</vector>\n"
  },
  {
    "path": "app/src/main/res/drawable/ic_baseline_delete_24.xml",
    "content": "<vector android:height=\"24dp\" android:tint=\"#FFFFFF\"\n    android:viewportHeight=\"24\" android:viewportWidth=\"24\"\n    android:width=\"24dp\" xmlns:android=\"http://schemas.android.com/apk/res/android\">\n    <path android:fillColor=\"@android:color/white\" android:pathData=\"M6,19c0,1.1 0.9,2 2,2h8c1.1,0 2,-0.9 2,-2V7H6v12zM19,4h-3.5l-1,-1h-5l-1,1H5v2h14V4z\"/>\n</vector>\n"
  },
  {
    "path": "app/src/main/res/drawable/ic_baseline_download_24.xml",
    "content": "<vector android:height=\"24dp\" android:tint=\"#FFFFFF\"\n    android:viewportHeight=\"24\" android:viewportWidth=\"24\"\n    android:width=\"24dp\" xmlns:android=\"http://schemas.android.com/apk/res/android\">\n    <path android:fillColor=\"@android:color/white\" android:pathData=\"M5,20h14v-2H5V20zM19,9h-4V3H9v6H5l7,7L19,9z\"/>\n</vector>\n"
  },
  {
    "path": "app/src/main/res/drawable/ic_baseline_favorite_24.xml",
    "content": "<vector android:height=\"24dp\" android:tint=\"@color/red\"\n    android:viewportHeight=\"24\" android:viewportWidth=\"24\"\n    android:width=\"24dp\" xmlns:android=\"http://schemas.android.com/apk/res/android\">\n    <path android:fillColor=\"@android:color/white\" android: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_baseline_favorite_border_24.xml",
    "content": "<vector android:height=\"24dp\" android:tint=\"#FFFFFF\"\n    android:viewportHeight=\"24\" android:viewportWidth=\"24\"\n    android:width=\"24dp\" xmlns:android=\"http://schemas.android.com/apk/res/android\">\n    <path android:fillColor=\"@android:color/white\" android: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,3zM12.1,18.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_baseline_help_24.xml",
    "content": "<vector android:autoMirrored=\"true\" android:height=\"24dp\"\n    android:tint=\"#FFFFFF\" android:viewportHeight=\"24\"\n    android:viewportWidth=\"24\" android:width=\"24dp\" xmlns:android=\"http://schemas.android.com/apk/res/android\">\n    <path android:fillColor=\"@android:color/white\" android:pathData=\"M12,2C6.48,2 2,6.48 2,12s4.48,10 10,10 10,-4.48 10,-10S17.52,2 12,2zM13,19h-2v-2h2v2zM15.07,11.25l-0.9,0.92C13.45,12.9 13,13.5 13,15h-2v-0.5c0,-1.1 0.45,-2.1 1.17,-2.83l1.24,-1.26c0.37,-0.36 0.59,-0.86 0.59,-1.41 0,-1.1 -0.9,-2 -2,-2s-2,0.9 -2,2L8,9c0,-2.21 1.79,-4 4,-4s4,1.79 4,4c0,0.88 -0.36,1.68 -0.93,2.25z\"/>\n</vector>\n"
  },
  {
    "path": "app/src/main/res/drawable/ic_baseline_history_24.xml",
    "content": "<vector android:height=\"24dp\" android:tint=\"#000000\"\n    android:viewportHeight=\"24\" android:viewportWidth=\"24\"\n    android:width=\"24dp\" xmlns:android=\"http://schemas.android.com/apk/res/android\">\n    <path android:fillColor=\"@android:color/white\" android:pathData=\"M13,3c-4.97,0 -9,4.03 -9,9L1,12l3.89,3.89 0.07,0.14L9,12L6,12c0,-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,-9zM12,8v5l4.28,2.54 0.72,-1.21 -3.5,-2.08L13.5,8L12,8z\"/>\n</vector>\n"
  },
  {
    "path": "app/src/main/res/drawable/ic_baseline_home_24.xml",
    "content": "<vector android:height=\"24dp\" android:tint=\"#000000\"\n    android:viewportHeight=\"24\" android:viewportWidth=\"24\"\n    android:width=\"24dp\" xmlns:android=\"http://schemas.android.com/apk/res/android\">\n    <path android:fillColor=\"@android:color/white\" android:pathData=\"M10,20v-6h4v6h5v-8h3L12,3 2,12h3v8z\"/>\n</vector>\n"
  },
  {
    "path": "app/src/main/res/drawable/ic_baseline_info_24.xml",
    "content": "<vector android:height=\"24dp\" android:tint=\"#FFFFFF\"\n    android:viewportHeight=\"24\" android:viewportWidth=\"24\"\n    android:width=\"24dp\" xmlns:android=\"http://schemas.android.com/apk/res/android\">\n    <path android:fillColor=\"@android:color/white\" android:pathData=\"M12,2C6.48,2 2,6.48 2,12s4.48,10 10,10 10,-4.48 10,-10S17.52,2 12,2zM13,17h-2v-6h2v6zM13,9h-2L11,7h2v2z\"/>\n</vector>\n"
  },
  {
    "path": "app/src/main/res/drawable/ic_baseline_language_24.xml",
    "content": "<vector android:height=\"24dp\" android:tint=\"#FFFFFF\"\n    android:viewportHeight=\"24\" android:viewportWidth=\"24\"\n    android:width=\"24dp\" xmlns:android=\"http://schemas.android.com/apk/res/android\">\n    <path android:fillColor=\"@android:color/white\" android: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,2zM18.92,8h-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,2L4.26,14zM5.08,16h2.95c0.32,1.25 0.78,2.45 1.38,3.56 -1.84,-0.63 -3.37,-1.9 -4.33,-3.56zM8.03,8L5.08,8c0.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,14L9.66,14c-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,2zM14.59,19.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.38c0.16,0.64 0.26,1.31 0.26,2s-0.1,1.36 -0.26,2h-3.38z\"/>\n</vector>\n"
  },
  {
    "path": "app/src/main/res/drawable/ic_baseline_list_24.xml",
    "content": "<vector android:autoMirrored=\"true\" android:height=\"24dp\"\n    android:tint=\"#000000\" android:viewportHeight=\"24\"\n    android:viewportWidth=\"24\" android:width=\"24dp\" xmlns:android=\"http://schemas.android.com/apk/res/android\">\n    <path android:fillColor=\"@android:color/white\" android:pathData=\"M3,13h2v-2L3,11v2zM3,17h2v-2L3,15v2zM3,9h2L5,7L3,7v2zM7,13h14v-2L7,11v2zM7,17h14v-2L7,15v2zM7,7v2h14L21,7L7,7z\"/>\n</vector>\n"
  },
  {
    "path": "app/src/main/res/drawable/ic_baseline_menu_24.xml",
    "content": "<vector android:height=\"24dp\" android:tint=\"#FFFFFF\"\n    android:viewportHeight=\"24\" android:viewportWidth=\"24\"\n    android:width=\"24dp\" xmlns:android=\"http://schemas.android.com/apk/res/android\">\n    <path android:fillColor=\"@android:color/white\" android:pathData=\"M3,18h18v-2L3,16v2zM3,13h18v-2L3,11v2zM3,6v2h18L21,6L3,6z\"/>\n</vector>\n"
  },
  {
    "path": "app/src/main/res/drawable/ic_baseline_more_time_24.xml",
    "content": "<vector android:height=\"24dp\" android:tint=\"#FFFFFF\"\n    android:viewportHeight=\"24\" android:viewportWidth=\"24\"\n    android:width=\"24dp\" xmlns:android=\"http://schemas.android.com/apk/res/android\">\n    <path android:fillColor=\"@android:color/white\" android:pathData=\"M10,8l0,6l4.7,2.9l0.8,-1.2l-4,-2.4l0,-5.3z\"/>\n    <path android:fillColor=\"@android:color/white\" android:pathData=\"M17.92,12c0.05,0.33 0.08,0.66 0.08,1c0,3.9 -3.1,7 -7,7s-7,-3.1 -7,-7c0,-3.9 3.1,-7 7,-7c0.7,0 1.37,0.1 2,0.29V4.23C12.36,4.08 11.69,4 11,4c-5,0 -9,4 -9,9s4,9 9,9s9,-4 9,-9c0,-0.34 -0.02,-0.67 -0.06,-1H17.92z\"/>\n    <path android:fillColor=\"@android:color/white\" android:pathData=\"M20,5l0,-3l-2,0l0,3l-3,0l0,2l3,0l0,3l2,0l0,-3l3,0l0,-2z\"/>\n</vector>\n"
  },
  {
    "path": "app/src/main/res/drawable/ic_baseline_newspaper_24.xml",
    "content": "<vector android:height=\"24dp\" android:tint=\"#FFFFFF\"\n    android:viewportHeight=\"24\" android:viewportWidth=\"24\"\n    android:width=\"24dp\" xmlns:android=\"http://schemas.android.com/apk/res/android\">\n    <path android:fillColor=\"@android:color/white\" android:pathData=\"M22,3l-1.67,1.67L18.67,3L17,4.67L15.33,3l-1.66,1.67L12,3l-1.67,1.67L8.67,3L7,4.67L5.33,3L3.67,4.67L2,3v16c0,1.1 0.9,2 2,2l16,0c1.1,0 2,-0.9 2,-2V3zM11,19H4v-6h7V19zM20,19h-7v-2h7V19zM20,15h-7v-2h7V15zM20,11H4V8h16V11z\"/>\n</vector>\n"
  },
  {
    "path": "app/src/main/res/drawable/ic_baseline_pause_24.xml",
    "content": "<vector android:height=\"24dp\" android:tint=\"#000000\"\n    android:viewportHeight=\"24\" android:viewportWidth=\"24\"\n    android:width=\"24dp\" xmlns:android=\"http://schemas.android.com/apk/res/android\">\n    <path android:fillColor=\"@android:color/white\" android:pathData=\"M6,19h4L10,5L6,5v14zM14,5v14h4L18,5h-4z\"/>\n</vector>\n"
  },
  {
    "path": "app/src/main/res/drawable/ic_baseline_pause_24_tintwhite.xml",
    "content": "<vector android:height=\"24dp\" android:tint=\"#FFFFFF\"\n    android:viewportHeight=\"24\" android:viewportWidth=\"24\"\n    android:width=\"24dp\" xmlns:android=\"http://schemas.android.com/apk/res/android\">\n    <path android:fillColor=\"@android:color/white\" android:pathData=\"M6,19h4L10,5L6,5v14zM14,5v14h4L18,5h-4z\"/>\n</vector>\n"
  },
  {
    "path": "app/src/main/res/drawable/ic_baseline_play_arrow_24.xml",
    "content": "<vector android:height=\"24dp\" android:tint=\"#000000\"\n    android:viewportHeight=\"24\" android:viewportWidth=\"24\"\n    android:width=\"24dp\" xmlns:android=\"http://schemas.android.com/apk/res/android\">\n    <path android:fillColor=\"@android:color/white\" android:pathData=\"M8,5v14l11,-7z\"/>\n</vector>\n"
  },
  {
    "path": "app/src/main/res/drawable/ic_baseline_play_arrow_24_tintwhite.xml",
    "content": "<vector android:height=\"24dp\" android:tint=\"#FFFFFF\"\n    android:viewportHeight=\"24\" android:viewportWidth=\"24\"\n    android:width=\"24dp\" xmlns:android=\"http://schemas.android.com/apk/res/android\">\n    <path android:fillColor=\"@android:color/white\" android:pathData=\"M8,5v14l11,-7z\"/>\n</vector>\n"
  },
  {
    "path": "app/src/main/res/drawable/ic_baseline_play_circle_outline_24.xml",
    "content": "<vector android:height=\"24dp\" android:tint=\"#FFFFFFFF\"\n    android:viewportHeight=\"24\" android:viewportWidth=\"24\"\n    android:width=\"24dp\" xmlns:android=\"http://schemas.android.com/apk/res/android\">\n    <path android:fillColor=\"@android:color/white\" android:pathData=\"M10,16.5l6,-4.5 -6,-4.5v9zM12,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,8z\"/>\n</vector>\n"
  },
  {
    "path": "app/src/main/res/drawable/ic_baseline_reply_24.xml",
    "content": "<vector android:autoMirrored=\"true\" android:height=\"24dp\"\n    android:tint=\"#000000\" android:viewportHeight=\"24\"\n    android:viewportWidth=\"24\" android:width=\"24dp\" xmlns:android=\"http://schemas.android.com/apk/res/android\">\n    <path android:fillColor=\"@android:color/white\" android:pathData=\"M10,9V5l-7,7 7,7v-4.1c5,0 8.5,1.6 11,5.1 -1,-5 -4,-10 -11,-11z\"/>\n</vector>\n"
  },
  {
    "path": "app/src/main/res/drawable/ic_baseline_search_24.xml",
    "content": "<vector android:height=\"24dp\" android:tint=\"#FFFFFFFF\"\n    android:viewportHeight=\"24\" android:viewportWidth=\"24\"\n    android:width=\"24dp\" xmlns:android=\"http://schemas.android.com/apk/res/android\">\n    <path android:fillColor=\"@android:color/white\" android:pathData=\"M15.5,14h-0.79l-0.28,-0.27C15.41,12.59 16,11.11 16,9.5 16,5.91 13.09,3 9.5,3S3,5.91 3,9.5 5.91,16 9.5,16c1.61,0 3.09,-0.59 4.23,-1.57l0.27,0.28v0.79l5,4.99L20.49,19l-4.99,-5zM9.5,14C7.01,14 5,11.99 5,9.5S7.01,5 9.5,5 14,7.01 14,9.5 11.99,14 9.5,14z\"/>\n</vector>\n"
  },
  {
    "path": "app/src/main/res/drawable/ic_baseline_send_24.xml",
    "content": "<vector android:autoMirrored=\"true\" android:height=\"24dp\"\n    android:tint=\"#000000\" android:viewportHeight=\"24\"\n    android:viewportWidth=\"24\" android:width=\"24dp\" xmlns:android=\"http://schemas.android.com/apk/res/android\">\n    <path android:fillColor=\"@android:color/white\" android:pathData=\"M2.01,21L23,12 2.01,3 2,10l15,2 -15,2z\"/>\n</vector>\n"
  },
  {
    "path": "app/src/main/res/drawable/ic_baseline_settings_24.xml",
    "content": "<vector android:height=\"24dp\" android:tint=\"#000000\"\n    android:viewportHeight=\"24\" android:viewportWidth=\"24\"\n    android:width=\"24dp\" xmlns:android=\"http://schemas.android.com/apk/res/android\">\n    <path android:fillColor=\"@android:color/white\" android:pathData=\"M19.14,12.94c0.04,-0.3 0.06,-0.61 0.06,-0.94c0,-0.32 -0.02,-0.64 -0.07,-0.94l2.03,-1.58c0.18,-0.14 0.23,-0.41 0.12,-0.61l-1.92,-3.32c-0.12,-0.22 -0.37,-0.29 -0.59,-0.22l-2.39,0.96c-0.5,-0.38 -1.03,-0.7 -1.62,-0.94L14.4,2.81c-0.04,-0.24 -0.24,-0.41 -0.48,-0.41h-3.84c-0.24,0 -0.43,0.17 -0.47,0.41L9.25,5.35C8.66,5.59 8.12,5.92 7.63,6.29L5.24,5.33c-0.22,-0.08 -0.47,0 -0.59,0.22L2.74,8.87C2.62,9.08 2.66,9.34 2.86,9.48l2.03,1.58C4.84,11.36 4.8,11.69 4.8,12s0.02,0.64 0.07,0.94l-2.03,1.58c-0.18,0.14 -0.23,0.41 -0.12,0.61l1.92,3.32c0.12,0.22 0.37,0.29 0.59,0.22l2.39,-0.96c0.5,0.38 1.03,0.7 1.62,0.94l0.36,2.54c0.05,0.24 0.24,0.41 0.48,0.41h3.84c0.24,0 0.44,-0.17 0.47,-0.41l0.36,-2.54c0.59,-0.24 1.13,-0.56 1.62,-0.94l2.39,0.96c0.22,0.08 0.47,0 0.59,-0.22l1.92,-3.32c0.12,-0.22 0.07,-0.47 -0.12,-0.61L19.14,12.94zM12,15.6c-1.98,0 -3.6,-1.62 -3.6,-3.6s1.62,-3.6 3.6,-3.6s3.6,1.62 3.6,3.6S13.98,15.6 12,15.6z\"/>\n</vector>\n"
  },
  {
    "path": "app/src/main/res/drawable/ic_baseline_share_24.xml",
    "content": "<vector android:height=\"24dp\" android:tint=\"#FFFFFF\"\n    android:viewportHeight=\"24\" android:viewportWidth=\"24\"\n    android:width=\"24dp\" xmlns:android=\"http://schemas.android.com/apk/res/android\">\n    <path android:fillColor=\"@android:color/white\" android:pathData=\"M18,16.08c-0.76,0 -1.44,0.3 -1.96,0.77L8.91,12.7c0.05,-0.23 0.09,-0.46 0.09,-0.7s-0.04,-0.47 -0.09,-0.7l7.05,-4.11c0.54,0.5 1.25,0.81 2.04,0.81 1.66,0 3,-1.34 3,-3s-1.34,-3 -3,-3 -3,1.34 -3,3c0,0.24 0.04,0.47 0.09,0.7L8.04,9.81C7.5,9.31 6.79,9 6,9c-1.66,0 -3,1.34 -3,3s1.34,3 3,3c0.79,0 1.5,-0.31 2.04,-0.81l7.12,4.16c-0.05,0.21 -0.08,0.43 -0.08,0.65 0,1.61 1.31,2.92 2.92,2.92 1.61,0 2.92,-1.31 2.92,-2.92s-1.31,-2.92 -2.92,-2.92z\"/>\n</vector>\n"
  },
  {
    "path": "app/src/main/res/drawable/ic_baseline_tag_24.xml",
    "content": "<vector android:height=\"24dp\" android:tint=\"#000000\"\n    android:viewportHeight=\"24\" android:viewportWidth=\"24\"\n    android:width=\"24dp\" xmlns:android=\"http://schemas.android.com/apk/res/android\">\n    <path android:fillColor=\"@android:color/white\" android:pathData=\"M20,10L20,8h-4L16,4h-2v4h-4L10,4L8,4v4L4,8v2h4v4L4,14v2h4v4h2v-4h4v4h2v-4h4v-2h-4v-4h4zM14,14h-4v-4h4v4z\"/>\n</vector>\n"
  },
  {
    "path": "app/src/main/res/drawable/ic_baseline_thumb_down_alt_24.xml",
    "content": "<vector android:height=\"24dp\" android:tint=\"#FFFFFF\"\n    android:viewportHeight=\"24\" android:viewportWidth=\"24\"\n    android:width=\"24dp\" xmlns:android=\"http://schemas.android.com/apk/res/android\">\n    <path android:fillColor=\"@android:color/white\" android:pathData=\"M22,4h-2c-0.55,0 -1,0.45 -1,1v9c0,0.55 0.45,1 1,1h2V4zM2.17,11.12c-0.11,0.25 -0.17,0.52 -0.17,0.8V13c0,1.1 0.9,2 2,2h5.5l-0.92,4.65c-0.05,0.22 -0.02,0.46 0.08,0.66 0.23,0.45 0.52,0.86 0.88,1.22L10,22l6.41,-6.41c0.38,-0.38 0.59,-0.89 0.59,-1.42V6.34C17,5.05 15.95,4 14.66,4h-8.1c-0.71,0 -1.36,0.37 -1.72,0.97l-2.67,6.15z\"/>\n</vector>\n"
  },
  {
    "path": "app/src/main/res/drawable/ic_baseline_thumb_down_off_alt_24.xml",
    "content": "<vector android:height=\"24dp\" android:tint=\"#FFFFFF\"\n    android:viewportHeight=\"24\" android:viewportWidth=\"24\"\n    android:width=\"24dp\" xmlns:android=\"http://schemas.android.com/apk/res/android\">\n    <path android:fillColor=\"@android:color/white\" android:pathData=\"M10.89,18.28l0.57,-2.89c0.12,-0.59 -0.04,-1.2 -0.42,-1.66 -0.38,-0.46 -0.94,-0.73 -1.54,-0.73L4,13v-1.08L6.57,6h8.09c0.18,0 0.34,0.16 0.34,0.34v7.84l-4.11,4.1M10,22l6.41,-6.41c0.38,-0.38 0.59,-0.89 0.59,-1.42L17,6.34C17,5.05 15.95,4 14.66,4h-8.1c-0.71,0 -1.36,0.37 -1.72,0.97l-2.67,6.15c-0.11,0.25 -0.17,0.52 -0.17,0.8L2,13c0,1.1 0.9,2 2,2h5.5l-0.92,4.65c-0.05,0.22 -0.02,0.46 0.08,0.66 0.23,0.45 0.52,0.86 0.88,1.22L10,22zM20,15h2L22,4h-2c-0.55,0 -1,0.45 -1,1v9c0,0.55 0.45,1 1,1z\"/>\n</vector>\n"
  },
  {
    "path": "app/src/main/res/drawable/ic_baseline_thumb_up_alt_24.xml",
    "content": "<vector android:height=\"24dp\" android:tint=\"#FFFFFF\"\n    android:viewportHeight=\"24\" android:viewportWidth=\"24\"\n    android:width=\"24dp\" xmlns:android=\"http://schemas.android.com/apk/res/android\">\n    <path android:fillColor=\"@android:color/white\" android:pathData=\"M2,20h2c0.55,0 1,-0.45 1,-1v-9c0,-0.55 -0.45,-1 -1,-1L2,9v11zM21.83,12.88c0.11,-0.25 0.17,-0.52 0.17,-0.8L22,11c0,-1.1 -0.9,-2 -2,-2h-5.5l0.92,-4.65c0.05,-0.22 0.02,-0.46 -0.08,-0.66 -0.23,-0.45 -0.52,-0.86 -0.88,-1.22L14,2 7.59,8.41C7.21,8.79 7,9.3 7,9.83v7.84C7,18.95 8.05,20 9.34,20h8.11c0.7,0 1.36,-0.37 1.72,-0.97l2.66,-6.15z\"/>\n</vector>\n"
  },
  {
    "path": "app/src/main/res/drawable/ic_baseline_thumb_up_off_alt_24.xml",
    "content": "<vector android:height=\"24dp\" android:tint=\"#FFFFFF\"\n    android:viewportHeight=\"24\" android:viewportWidth=\"24\"\n    android:width=\"24dp\" xmlns:android=\"http://schemas.android.com/apk/res/android\">\n    <path android:fillColor=\"@android:color/white\" android:pathData=\"M13.11,5.72l-0.57,2.89c-0.12,0.59 0.04,1.2 0.42,1.66 0.38,0.46 0.94,0.73 1.54,0.73H20v1.08L17.43,18H9.34c-0.18,0 -0.34,-0.16 -0.34,-0.34V9.82l4.11,-4.1M14,2L7.59,8.41C7.21,8.79 7,9.3 7,9.83v7.83C7,18.95 8.05,20 9.34,20h8.1c0.71,0 1.36,-0.37 1.72,-0.97l2.67,-6.15c0.11,-0.25 0.17,-0.52 0.17,-0.8V11c0,-1.1 -0.9,-2 -2,-2h-5.5l0.92,-4.65c0.05,-0.22 0.02,-0.46 -0.08,-0.66 -0.23,-0.45 -0.52,-0.86 -0.88,-1.22L14,2zM4,9H2v11h2c0.55,0 1,-0.45 1,-1v-9c0,-0.55 -0.45,-1 -1,-1z\"/>\n</vector>\n"
  },
  {
    "path": "app/src/main/res/drawable/ic_baseline_update_24.xml",
    "content": "<vector android:height=\"24dp\" android:tint=\"#FFFFFF\"\n    android:viewportHeight=\"24\" android:viewportWidth=\"24\"\n    android:width=\"24dp\" xmlns:android=\"http://schemas.android.com/apk/res/android\">\n    <path android:fillColor=\"@android:color/white\" android:pathData=\"M21,10.12h-6.78l2.74,-2.82c-2.73,-2.7 -7.15,-2.8 -9.88,-0.1c-2.73,2.71 -2.73,7.08 0,9.79s7.15,2.71 9.88,0C18.32,15.65 19,14.08 19,12.1h2c0,1.98 -0.88,4.55 -2.64,6.29c-3.51,3.48 -9.21,3.48 -12.72,0c-3.5,-3.47 -3.53,-9.11 -0.02,-12.58s9.14,-3.47 12.65,0L21,3V10.12zM12.5,8v4.25l3.5,2.08l-0.72,1.21L11,13V8H12.5z\"/>\n</vector>\n"
  },
  {
    "path": "app/src/main/res/drawable/ic_baseline_watch_later_24.xml",
    "content": "<vector android:height=\"24dp\" android:tint=\"#FFFFFF\"\n    android:viewportHeight=\"24\" android:viewportWidth=\"24\"\n    android:width=\"24dp\" xmlns:android=\"http://schemas.android.com/apk/res/android\">\n    <path android:fillColor=\"@android:color/white\" android:pathData=\"M12,2C6.5,2 2,6.5 2,12s4.5,10 10,10s10,-4.5 10,-10S17.5,2 12,2zM16.2,16.2L11,13V7h1.5v5.2l4.5,2.7L16.2,16.2z\"/>\n</vector>\n"
  },
  {
    "path": "app/src/main/res/drawable/ic_launcher_background.xml",
    "content": "<?xml version=\"1.0\" encoding=\"utf-8\"?>\n<vector xmlns:android=\"http://schemas.android.com/apk/res/android\"\n    android:width=\"108dp\"\n    android:height=\"108dp\"\n    android:viewportWidth=\"108\"\n    android:viewportHeight=\"108\">\n    <path\n        android:fillColor=\"#3DDC84\"\n        android:pathData=\"M0,0h108v108h-108z\" />\n    <path\n        android:fillColor=\"#00000000\"\n        android:pathData=\"M9,0L9,108\"\n        android:strokeWidth=\"0.8\"\n        android:strokeColor=\"#33FFFFFF\" />\n    <path\n        android:fillColor=\"#00000000\"\n        android:pathData=\"M19,0L19,108\"\n        android:strokeWidth=\"0.8\"\n        android:strokeColor=\"#33FFFFFF\" />\n    <path\n        android:fillColor=\"#00000000\"\n        android:pathData=\"M29,0L29,108\"\n        android:strokeWidth=\"0.8\"\n        android:strokeColor=\"#33FFFFFF\" />\n    <path\n        android:fillColor=\"#00000000\"\n        android:pathData=\"M39,0L39,108\"\n        android:strokeWidth=\"0.8\"\n        android:strokeColor=\"#33FFFFFF\" />\n    <path\n        android:fillColor=\"#00000000\"\n        android:pathData=\"M49,0L49,108\"\n        android:strokeWidth=\"0.8\"\n        android:strokeColor=\"#33FFFFFF\" />\n    <path\n        android:fillColor=\"#00000000\"\n        android:pathData=\"M59,0L59,108\"\n        android:strokeWidth=\"0.8\"\n        android:strokeColor=\"#33FFFFFF\" />\n    <path\n        android:fillColor=\"#00000000\"\n        android:pathData=\"M69,0L69,108\"\n        android:strokeWidth=\"0.8\"\n        android:strokeColor=\"#33FFFFFF\" />\n    <path\n        android:fillColor=\"#00000000\"\n        android:pathData=\"M79,0L79,108\"\n        android:strokeWidth=\"0.8\"\n        android:strokeColor=\"#33FFFFFF\" />\n    <path\n        android:fillColor=\"#00000000\"\n        android:pathData=\"M89,0L89,108\"\n        android:strokeWidth=\"0.8\"\n        android:strokeColor=\"#33FFFFFF\" />\n    <path\n        android:fillColor=\"#00000000\"\n        android:pathData=\"M99,0L99,108\"\n        android:strokeWidth=\"0.8\"\n        android:strokeColor=\"#33FFFFFF\" />\n    <path\n        android:fillColor=\"#00000000\"\n        android:pathData=\"M0,9L108,9\"\n        android:strokeWidth=\"0.8\"\n        android:strokeColor=\"#33FFFFFF\" />\n    <path\n        android:fillColor=\"#00000000\"\n        android:pathData=\"M0,19L108,19\"\n        android:strokeWidth=\"0.8\"\n        android:strokeColor=\"#33FFFFFF\" />\n    <path\n        android:fillColor=\"#00000000\"\n        android:pathData=\"M0,29L108,29\"\n        android:strokeWidth=\"0.8\"\n        android:strokeColor=\"#33FFFFFF\" />\n    <path\n        android:fillColor=\"#00000000\"\n        android:pathData=\"M0,39L108,39\"\n        android:strokeWidth=\"0.8\"\n        android:strokeColor=\"#33FFFFFF\" />\n    <path\n        android:fillColor=\"#00000000\"\n        android:pathData=\"M0,49L108,49\"\n        android:strokeWidth=\"0.8\"\n        android:strokeColor=\"#33FFFFFF\" />\n    <path\n        android:fillColor=\"#00000000\"\n        android:pathData=\"M0,59L108,59\"\n        android:strokeWidth=\"0.8\"\n        android:strokeColor=\"#33FFFFFF\" />\n    <path\n        android:fillColor=\"#00000000\"\n        android:pathData=\"M0,69L108,69\"\n        android:strokeWidth=\"0.8\"\n        android:strokeColor=\"#33FFFFFF\" />\n    <path\n        android:fillColor=\"#00000000\"\n        android:pathData=\"M0,79L108,79\"\n        android:strokeWidth=\"0.8\"\n        android:strokeColor=\"#33FFFFFF\" />\n    <path\n        android:fillColor=\"#00000000\"\n        android:pathData=\"M0,89L108,89\"\n        android:strokeWidth=\"0.8\"\n        android:strokeColor=\"#33FFFFFF\" />\n    <path\n        android:fillColor=\"#00000000\"\n        android:pathData=\"M0,99L108,99\"\n        android:strokeWidth=\"0.8\"\n        android:strokeColor=\"#33FFFFFF\" />\n    <path\n        android:fillColor=\"#00000000\"\n        android:pathData=\"M19,29L89,29\"\n        android:strokeWidth=\"0.8\"\n        android:strokeColor=\"#33FFFFFF\" />\n    <path\n        android:fillColor=\"#00000000\"\n        android:pathData=\"M19,39L89,39\"\n        android:strokeWidth=\"0.8\"\n        android:strokeColor=\"#33FFFFFF\" />\n    <path\n        android:fillColor=\"#00000000\"\n        android:pathData=\"M19,49L89,49\"\n        android:strokeWidth=\"0.8\"\n        android:strokeColor=\"#33FFFFFF\" />\n    <path\n        android:fillColor=\"#00000000\"\n        android:pathData=\"M19,59L89,59\"\n        android:strokeWidth=\"0.8\"\n        android:strokeColor=\"#33FFFFFF\" />\n    <path\n        android:fillColor=\"#00000000\"\n        android:pathData=\"M19,69L89,69\"\n        android:strokeWidth=\"0.8\"\n        android:strokeColor=\"#33FFFFFF\" />\n    <path\n        android:fillColor=\"#00000000\"\n        android:pathData=\"M19,79L89,79\"\n        android:strokeWidth=\"0.8\"\n        android:strokeColor=\"#33FFFFFF\" />\n    <path\n        android:fillColor=\"#00000000\"\n        android:pathData=\"M29,19L29,89\"\n        android:strokeWidth=\"0.8\"\n        android:strokeColor=\"#33FFFFFF\" />\n    <path\n        android:fillColor=\"#00000000\"\n        android:pathData=\"M39,19L39,89\"\n        android:strokeWidth=\"0.8\"\n        android:strokeColor=\"#33FFFFFF\" />\n    <path\n        android:fillColor=\"#00000000\"\n        android:pathData=\"M49,19L49,89\"\n        android:strokeWidth=\"0.8\"\n        android:strokeColor=\"#33FFFFFF\" />\n    <path\n        android:fillColor=\"#00000000\"\n        android:pathData=\"M59,19L59,89\"\n        android:strokeWidth=\"0.8\"\n        android:strokeColor=\"#33FFFFFF\" />\n    <path\n        android:fillColor=\"#00000000\"\n        android:pathData=\"M69,19L69,89\"\n        android:strokeWidth=\"0.8\"\n        android:strokeColor=\"#33FFFFFF\" />\n    <path\n        android:fillColor=\"#00000000\"\n        android:pathData=\"M79,19L79,89\"\n        android:strokeWidth=\"0.8\"\n        android:strokeColor=\"#33FFFFFF\" />\n</vector>\n"
  },
  {
    "path": "app/src/main/res/drawable/ic_outline_watch_later_24.xml",
    "content": "<vector android:height=\"24dp\" android:tint=\"#FFFFFF\"\n    android:viewportHeight=\"24\" android:viewportWidth=\"24\"\n    android:width=\"24dp\" xmlns:android=\"http://schemas.android.com/apk/res/android\">\n    <path android:fillColor=\"@android:color/white\" android:pathData=\"M12,2C6.5,2 2,6.5 2,12s4.5,10 10,10s10,-4.5 10,-10S17.5,2 12,2zM12,20c-4.41,0 -8,-3.59 -8,-8s3.59,-8 8,-8s8,3.59 8,8S16.41,20 12,20zM12.5,7H11v6l5.2,3.2l0.8,-1.3l-4.5,-2.7V7z\"/>\n</vector>\n"
  },
  {
    "path": "app/src/main/res/drawable/line_divider.xml",
    "content": "<?xml version=\"1.0\" encoding=\"utf-8\"?>\n<shape xmlns:android=\"http://schemas.android.com/apk/res/android\"\n    android:shape=\"rectangle\">\n\n    <solid android:color=\"@color/title_mask_color\" />\n\n    <size android:height=\"1dp\" />\n\n    <corners android:radius=\"8dp\" />\n\n</shape>"
  },
  {
    "path": "app/src/main/res/drawable/menu_24px.xml",
    "content": "<vector xmlns:android=\"http://schemas.android.com/apk/res/android\"\n    android:width=\"24dp\"\n    android:height=\"24dp\"\n    android:viewportWidth=\"960\"\n    android:viewportHeight=\"960\"\n    android:tint=\"?attr/colorControlNormal\">\n  <path\n      android:fillColor=\"@android:color/white\"\n      android:pathData=\"M120,720L120,640L840,640L840,720L120,720ZM120,520L120,440L840,440L840,520L120,520ZM120,320L120,240L840,240L840,320L120,320Z\"/>\n</vector>\n"
  },
  {
    "path": "app/src/main/res/drawable/open_in_new_24px.xml",
    "content": "<vector xmlns:android=\"http://schemas.android.com/apk/res/android\"\n    android:width=\"24dp\"\n    android:height=\"24dp\"\n    android:viewportWidth=\"960\"\n    android:viewportHeight=\"960\"\n    android:tint=\"?attr/colorControlNormal\"\n    android:autoMirrored=\"true\">\n  <path\n      android:fillColor=\"@android:color/white\"\n      android:pathData=\"M200,840Q167,840 143.5,816.5Q120,793 120,760L120,200Q120,167 143.5,143.5Q167,120 200,120L480,120L480,200L200,200Q200,200 200,200Q200,200 200,200L200,760Q200,760 200,760Q200,760 200,760L760,760Q760,760 760,760Q760,760 760,760L760,480L840,480L840,760Q840,793 816.5,816.5Q793,840 760,840L200,840ZM388,628L332,572L704,200L560,200L560,120L840,120L840,400L760,400L760,256L388,628Z\"/>\n</vector>\n"
  },
  {
    "path": "app/src/main/res/drawable/outline_home_24.xml",
    "content": "<vector android:height=\"24dp\" android:tint=\"#FFFFFF\"\n    android:viewportHeight=\"24\" android:viewportWidth=\"24\"\n    android:width=\"24dp\" xmlns:android=\"http://schemas.android.com/apk/res/android\">\n    <path android:fillColor=\"@android:color/white\" android:pathData=\"M12,5.69l5,4.5V18h-2v-6H9v6H7v-7.81l5,-4.5M12,3L2,12h3v8h6v-6h2v6h6v-8h3L12,3z\"/>\n</vector>\n"
  },
  {
    "path": "app/src/main/res/drawable/pause_circle_24px.xml",
    "content": "<vector xmlns:android=\"http://schemas.android.com/apk/res/android\"\n    android:width=\"24dp\"\n    android:height=\"24dp\"\n    android:viewportWidth=\"960\"\n    android:viewportHeight=\"960\"\n    android:tint=\"?attr/colorControlNormal\">\n  <path\n      android:fillColor=\"@android:color/white\"\n      android:pathData=\"M360,640L440,640L440,320L360,320L360,640ZM520,640L600,640L600,320L520,320L520,640ZM480,880Q397,880 324,848.5Q251,817 197,763Q143,709 111.5,636Q80,563 80,480Q80,397 111.5,324Q143,251 197,197Q251,143 324,111.5Q397,80 480,80Q563,80 636,111.5Q709,143 763,197Q817,251 848.5,324Q880,397 880,480Q880,563 848.5,636Q817,709 763,763Q709,817 636,848.5Q563,880 480,880ZM480,800Q614,800 707,707Q800,614 800,480Q800,346 707,253Q614,160 480,160Q346,160 253,253Q160,346 160,480Q160,614 253,707Q346,800 480,800ZM480,480Q480,480 480,480Q480,480 480,480Q480,480 480,480Q480,480 480,480Q480,480 480,480Q480,480 480,480Q480,480 480,480Q480,480 480,480Z\"/>\n</vector>\n"
  },
  {
    "path": "app/src/main/res/drawable/pip_24px.xml",
    "content": "<vector xmlns:android=\"http://schemas.android.com/apk/res/android\"\n    android:width=\"24dp\"\n    android:height=\"24dp\"\n    android:viewportWidth=\"960\"\n    android:viewportHeight=\"960\"\n    android:tint=\"?attr/colorControlNormal\">\n  <path\n      android:fillColor=\"@android:color/white\"\n      android:pathData=\"M80,440L80,360L224,360L52,188L108,132L280,304L280,160L360,160L360,440L80,440ZM160,800Q127,800 103.5,776.5Q80,753 80,720L80,520L160,520L160,720Q160,720 160,720Q160,720 160,720L480,720L480,800L160,800ZM800,520L800,240Q800,240 800,240Q800,240 800,240L440,240L440,160L800,160Q833,160 856.5,183.5Q880,207 880,240L880,520L800,520ZM560,800L560,600L880,600L880,800L560,800Z\"/>\n</vector>\n"
  },
  {
    "path": "app/src/main/res/drawable/play_circle_24px.xml",
    "content": "<vector xmlns:android=\"http://schemas.android.com/apk/res/android\"\n    android:width=\"24dp\"\n    android:height=\"24dp\"\n    android:viewportWidth=\"960\"\n    android:viewportHeight=\"960\"\n    android:tint=\"?attr/colorControlNormal\">\n  <path\n      android:fillColor=\"@android:color/white\"\n      android:pathData=\"M380,660L660,480L380,300L380,660ZM480,880Q397,880 324,848.5Q251,817 197,763Q143,709 111.5,636Q80,563 80,480Q80,397 111.5,324Q143,251 197,197Q251,143 324,111.5Q397,80 480,80Q563,80 636,111.5Q709,143 763,197Q817,251 848.5,324Q880,397 880,480Q880,563 848.5,636Q817,709 763,763Q709,817 636,848.5Q563,880 480,880ZM480,800Q614,800 707,707Q800,614 800,480Q800,346 707,253Q614,160 480,160Q346,160 253,253Q160,346 160,480Q160,614 253,707Q346,800 480,800ZM480,480Q480,480 480,480Q480,480 480,480Q480,480 480,480Q480,480 480,480Q480,480 480,480Q480,480 480,480Q480,480 480,480Q480,480 480,480Z\"/>\n</vector>\n"
  },
  {
    "path": "app/src/main/res/drawable/scaled_added_time.xml",
    "content": "<?xml version=\"1.0\" encoding=\"utf-8\"?>\n<layer-list xmlns:android=\"http://schemas.android.com/apk/res/android\">\n\n    <!-- 用sp而不用dp是爲了能和文字大小一樣 -->\n\n    <item\n        android:width=\"14sp\"\n        android:height=\"14sp\"\n        android:drawable=\"@drawable/ic_baseline_more_time_24\" />\n\n</layer-list>"
  },
  {
    "path": "app/src/main/res/drawable/screenshot_frame_2_24px.xml",
    "content": "<vector xmlns:android=\"http://schemas.android.com/apk/res/android\"\n    android:width=\"24dp\"\n    android:height=\"24dp\"\n    android:viewportWidth=\"960\"\n    android:viewportHeight=\"960\"\n    android:tint=\"?attr/colorControlNormal\">\n  <path\n      android:fillColor=\"@android:color/white\"\n      android:pathData=\"M800,400L800,280Q800,280 800,280Q800,280 800,280L680,280L680,200L800,200Q833,200 856.5,223.5Q880,247 880,280L880,400L800,400ZM80,400L80,280Q80,247 103.5,223.5Q127,200 160,200L280,200L280,280L160,280Q160,280 160,280Q160,280 160,280L160,400L80,400ZM680,760L680,680L800,680Q800,680 800,680Q800,680 800,680L800,560L880,560L880,680Q880,713 856.5,736.5Q833,760 800,760L680,760ZM160,760Q127,760 103.5,736.5Q80,713 80,680L80,560L160,560L160,680Q160,680 160,680Q160,680 160,680L280,680L280,760L160,760Z\"/>\n</vector>\n"
  },
  {
    "path": "app/src/main/res/drawable/search_bar.xml",
    "content": "<shape xmlns:android=\"http://schemas.android.com/apk/res/android\"\n    android:shape=\"rectangle\">\n    <!-- rectangle表示为矩形 -->\n\n    <solid android:color=\"@color/per90_transparent_white\" />\n\n    <!-- android:radius 圆角的半径 -->\n    <corners android:radius=\"8dp\" />\n</shape>"
  },
  {
    "path": "app/src/main/res/drawable/shape_download_func_bar.xml",
    "content": "<?xml version=\"1.0\" encoding=\"utf-8\"?>\n<shape xmlns:android=\"http://schemas.android.com/apk/res/android\" android:shape=\"rectangle\">\n    <corners android:radius=\"800dp\" />\n    <solid android:color=\"@color/per10_transparent_white\" />\n\n</shape>"
  },
  {
    "path": "app/src/main/res/drawable/shape_preview_button_left.xml",
    "content": "<?xml version=\"1.0\" encoding=\"utf-8\"?>\n<shape xmlns:android=\"http://schemas.android.com/apk/res/android\">\n    <corners\n        android:bottomLeftRadius=\"0dp\"\n        android:bottomRightRadius=\"16dp\"\n        android:topLeftRadius=\"0dp\"\n        android:topRightRadius=\"16dp\" />\n    \n</shape>"
  },
  {
    "path": "app/src/main/res/drawable/shape_preview_button_right.xml",
    "content": "<?xml version=\"1.0\" encoding=\"utf-8\"?>\n<shape xmlns:android=\"http://schemas.android.com/apk/res/android\">\n    <corners\n        android:bottomLeftRadius=\"16dp\"\n        android:bottomRightRadius=\"0dp\"\n        android:topLeftRadius=\"16dp\"\n        android:topRightRadius=\"0dp\" />\n</shape>"
  },
  {
    "path": "app/src/main/res/drawable/shape_reply_show_bottom_dialog.xml",
    "content": "<?xml version=\"1.0\" encoding=\"utf-8\"?>\n<shape xmlns:android=\"http://schemas.android.com/apk/res/android\">\n    <corners\n        android:topLeftRadius=\"16dp\"\n        android:topRightRadius=\"16dp\" />\n    <solid android:color=\"@color/per70_transparent_black\" />\n</shape>"
  },
  {
    "path": "app/src/main/res/drawable/shape_tag_selector.xml",
    "content": "<?xml version=\"1.0\" encoding=\"utf-8\"?>\n<shape xmlns:android=\"http://schemas.android.com/apk/res/android\">\n\n    <corners\n        android:topLeftRadius=\"16dp\"\n        android:topRightRadius=\"16dp\" />\n\n    <solid android:color=\"?android:colorBackground\" />\n\n</shape>"
  },
  {
    "path": "app/src/main/res/drawable/shape_title_mask.xml",
    "content": "<?xml version=\"1.0\" encoding=\"utf-8\"?>\n<shape xmlns:android=\"http://schemas.android.com/apk/res/android\"\n    android:shape=\"rectangle\">\n\n    <solid android:color=\"@color/title_mask_color\" />\n\n    <corners android:radius=\"8dp\" />\n\n</shape>"
  },
  {
    "path": "app/src/main/res/drawable/shape_title_mask_black.xml",
    "content": "<?xml version=\"1.0\" encoding=\"utf-8\"?>\n<shape xmlns:android=\"http://schemas.android.com/apk/res/android\"\n    android:shape=\"rectangle\">\n\n    <solid android:color=\"@color/per60_transparent_black\" />\n\n    <corners android:radius=\"8dp\" />\n\n</shape>"
  },
  {
    "path": "app/src/main/res/drawable/translated_border.xml",
    "content": "<?xml version=\"1.0\" encoding=\"utf-8\"?>\n<shape xmlns:android=\"http://schemas.android.com/apk/res/android\"\n    android:shape=\"rectangle\">\n\n    <solid android:color=\"@android:color/transparent\" />\n\n    <stroke\n        android:width=\"2dip\"\n        android:color=\"@color/red\" />\n\n    <corners android:radius=\"16dp\" />\n\n</shape>"
  },
  {
    "path": "app/src/main/res/drawable-v24/ic_launcher_foreground.xml",
    "content": "<vector xmlns:android=\"http://schemas.android.com/apk/res/android\"\n    xmlns:aapt=\"http://schemas.android.com/aapt\"\n    android:width=\"108dp\"\n    android:height=\"108dp\"\n    android:viewportWidth=\"108\"\n    android:viewportHeight=\"108\">\n    <path android:pathData=\"M31,63.928c0,0 6.4,-11 12.1,-13.1c7.2,-2.6 26,-1.4 26,-1.4l38.1,38.1L107,108.928l-32,-1L31,63.928z\">\n        <aapt:attr name=\"android:fillColor\">\n            <gradient\n                android:endX=\"85.84757\"\n                android:endY=\"92.4963\"\n                android:startX=\"42.9492\"\n                android:startY=\"49.59793\"\n                android:type=\"linear\">\n                <item\n                    android:color=\"#44000000\"\n                    android:offset=\"0.0\" />\n                <item\n                    android:color=\"#00000000\"\n                    android:offset=\"1.0\" />\n            </gradient>\n        </aapt:attr>\n    </path>\n    <path\n        android:fillColor=\"#FFFFFF\"\n        android:fillType=\"nonZero\"\n        android:pathData=\"M65.3,45.828l3.8,-6.6c0.2,-0.4 0.1,-0.9 -0.3,-1.1c-0.4,-0.2 -0.9,-0.1 -1.1,0.3l-3.9,6.7c-6.3,-2.8 -13.4,-2.8 -19.7,0l-3.9,-6.7c-0.2,-0.4 -0.7,-0.5 -1.1,-0.3C38.8,38.328 38.7,38.828 38.9,39.228l3.8,6.6C36.2,49.428 31.7,56.028 31,63.928h46C76.3,56.028 71.8,49.428 65.3,45.828zM43.4,57.328c-0.8,0 -1.5,-0.5 -1.8,-1.2c-0.3,-0.7 -0.1,-1.5 0.4,-2.1c0.5,-0.5 1.4,-0.7 2.1,-0.4c0.7,0.3 1.2,1 1.2,1.8C45.3,56.528 44.5,57.328 43.4,57.328L43.4,57.328zM64.6,57.328c-0.8,0 -1.5,-0.5 -1.8,-1.2s-0.1,-1.5 0.4,-2.1c0.5,-0.5 1.4,-0.7 2.1,-0.4c0.7,0.3 1.2,1 1.2,1.8C66.5,56.528 65.6,57.328 64.6,57.328L64.6,57.328z\"\n        android:strokeWidth=\"1\"\n        android:strokeColor=\"#00000000\" />\n</vector>"
  },
  {
    "path": "app/src/main/res/layout/activity_download.xml",
    "content": "<?xml version=\"1.0\" encoding=\"utf-8\"?>\n<layout xmlns:android=\"http://schemas.android.com/apk/res/android\"\n    xmlns:app=\"http://schemas.android.com/apk/res-auto\">\n\n    <data>\n\n    </data>\n\n    <androidx.coordinatorlayout.widget.CoordinatorLayout\n        android:id=\"@+id/coordinator\"\n        android:layout_width=\"match_parent\"\n        android:layout_height=\"match_parent\"\n        android:fitsSystemWindows=\"true\">\n\n        <com.google.android.material.appbar.AppBarLayout\n            android:layout_width=\"match_parent\"\n            android:layout_height=\"wrap_content\"\n            android:fitsSystemWindows=\"true\">\n\n            <androidx.appcompat.widget.Toolbar\n                android:id=\"@+id/toolbar\"\n                android:layout_width=\"match_parent\"\n                android:layout_height=\"?attr/actionBarSize\">\n\n            </androidx.appcompat.widget.Toolbar>\n\n            <com.google.android.material.tabs.TabLayout\n                android:id=\"@+id/tab_layout\"\n                android:layout_width=\"match_parent\"\n                android:layout_height=\"wrap_content\" />\n\n        </com.google.android.material.appbar.AppBarLayout>\n\n\n        <androidx.viewpager2.widget.ViewPager2\n            android:id=\"@+id/view_pager\"\n            android:layout_width=\"match_parent\"\n            android:layout_height=\"match_parent\"\n            app:layout_behavior=\"@string/appbar_scrolling_view_behavior\" />\n\n        <com.yenaly.han1meviewer.ui.view.funcbar.Hanidock\n            android:id=\"@+id/hanidock\"\n            android:layout_width=\"wrap_content\"\n            android:layout_height=\"wrap_content\"\n            android:layout_gravity=\"end|bottom\"\n            android:layout_margin=\"16dp\" />\n\n    </androidx.coordinatorlayout.widget.CoordinatorLayout>\n\n\n</layout>"
  },
  {
    "path": "app/src/main/res/layout/activity_login.xml",
    "content": "<?xml version=\"1.0\" encoding=\"utf-8\"?>\n<layout 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\n    <data>\n\n    </data>\n\n    <androidx.coordinatorlayout.widget.CoordinatorLayout\n        android:layout_width=\"match_parent\"\n        android:layout_height=\"match_parent\"\n        android:fitsSystemWindows=\"true\"\n        tools:context=\".ui.activity.LoginActivity\">\n\n        <com.google.android.material.appbar.AppBarLayout\n            android:layout_width=\"match_parent\"\n            android:layout_height=\"wrap_content\"\n            android:fitsSystemWindows=\"true\">\n\n            <androidx.appcompat.widget.Toolbar\n                android:id=\"@+id/toolbar\"\n                android:layout_width=\"match_parent\"\n                android:layout_height=\"?attr/actionBarSize\">\n\n            </androidx.appcompat.widget.Toolbar>\n\n        </com.google.android.material.appbar.AppBarLayout>\n\n        <com.scwang.smart.refresh.layout.SmartRefreshLayout\n            android:id=\"@+id/srl_login\"\n            android:layout_width=\"match_parent\"\n            android:layout_height=\"match_parent\"\n            app:layout_behavior=\"@string/appbar_scrolling_view_behavior\"\n            app:srlEnableLoadMore=\"false\">\n\n            <com.scwang.smart.refresh.header.MaterialHeader\n                android:layout_width=\"match_parent\"\n                android:layout_height=\"wrap_content\" />\n\n            <WebView\n                android:id=\"@+id/wv_login\"\n                android:layout_width=\"match_parent\"\n                android:layout_height=\"match_parent\" />\n\n        </com.scwang.smart.refresh.layout.SmartRefreshLayout>\n\n    </androidx.coordinatorlayout.widget.CoordinatorLayout>\n\n</layout>"
  },
  {
    "path": "app/src/main/res/layout/activity_main.xml",
    "content": "<?xml version=\"1.0\" encoding=\"utf-8\"?>\n<layout 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\n    <data>\n\n    </data>\n\n    <androidx.drawerlayout.widget.DrawerLayout\n        android:id=\"@+id/dl_main\"\n        android:layout_width=\"match_parent\"\n        android:layout_height=\"match_parent\"\n        android:fitsSystemWindows=\"true\">\n\n\n        <androidx.fragment.app.FragmentContainerView\n            android:id=\"@+id/fcv_main\"\n            android:name=\"androidx.navigation.fragment.NavHostFragment\"\n            android:layout_width=\"match_parent\"\n            android:layout_height=\"match_parent\"\n            android:fitsSystemWindows=\"true\"\n            app:defaultNavHost=\"true\"\n            app:layout_behavior=\"@string/appbar_scrolling_view_behavior\"\n            app:navGraph=\"@navigation/nav_main\" />\n\n        <com.google.android.material.navigation.NavigationView\n            android:id=\"@+id/nv_main\"\n            android:layout_width=\"wrap_content\"\n            android:layout_height=\"match_parent\"\n            android:layout_gravity=\"start\"\n            android:fitsSystemWindows=\"true\"\n            app:headerLayout=\"@layout/nav_header_ability\"\n            app:menu=\"@menu/menu_main_nv\" />\n\n    </androidx.drawerlayout.widget.DrawerLayout>\n\n</layout>"
  },
  {
    "path": "app/src/main/res/layout/activity_preview.xml",
    "content": "<?xml version=\"1.0\" encoding=\"utf-8\"?>\n<layout xmlns:android=\"http://schemas.android.com/apk/res/android\"\n    xmlns:app=\"http://schemas.android.com/apk/res-auto\">\n\n    <data>\n\n    </data>\n\n    <androidx.coordinatorlayout.widget.CoordinatorLayout\n        android:layout_width=\"match_parent\"\n        android:layout_height=\"match_parent\"\n        android:orientation=\"vertical\">\n\n        <com.google.android.material.appbar.AppBarLayout\n            android:id=\"@+id/app_bar\"\n            android:layout_width=\"match_parent\"\n            android:layout_height=\"wrap_content\"\n            android:fitsSystemWindows=\"true\">\n\n            <com.google.android.material.appbar.CollapsingToolbarLayout\n                android:id=\"@+id/collapsing_toolbar\"\n                android:layout_width=\"match_parent\"\n                android:layout_height=\"240dp\"\n                android:fitsSystemWindows=\"true\"\n                app:layout_scrollFlags=\"exitUntilCollapsed|scroll\"\n                app:titleEnabled=\"false\">\n\n                <FrameLayout\n                    android:layout_width=\"match_parent\"\n                    android:layout_height=\"match_parent\"\n                    android:fitsSystemWindows=\"true\">\n\n                    <ImageView\n                        android:id=\"@+id/cover\"\n                        android:layout_width=\"match_parent\"\n                        android:layout_height=\"match_parent\"\n                        android:contentDescription=\"@null\"\n                        android:fitsSystemWindows=\"true\"\n                        android:scaleType=\"centerCrop\"\n                        app:layout_collapseMode=\"parallax\" />\n\n                    <View\n                        android:layout_width=\"match_parent\"\n                        android:layout_height=\"match_parent\"\n                        android:background=\"@color/per50_transparent_black\"\n                        android:fitsSystemWindows=\"true\" />\n\n                    <com.google.android.material.button.MaterialButton\n                        android:id=\"@+id/fab_previous\"\n                        android:layout_width=\"wrap_content\"\n                        android:layout_height=\"wrap_content\"\n                        android:layout_gravity=\"bottom|start\"\n                        android:layout_marginBottom=\"16dp\"\n                        android:background=\"@drawable/shape_preview_button_left\"\n                        android:foreground=\"?selectableItemBackground\"\n                        app:icon=\"@drawable/ic_baseline_arrow_back_24\"\n                        app:iconGravity=\"start\" />\n\n                    <com.google.android.material.button.MaterialButton\n                        android:id=\"@+id/fab_next\"\n                        android:layout_width=\"wrap_content\"\n                        android:layout_height=\"wrap_content\"\n                        android:layout_gravity=\"bottom|end\"\n                        android:layout_marginBottom=\"16dp\"\n                        android:background=\"@drawable/shape_preview_button_right\"\n                        android:foreground=\"?selectableItemBackground\"\n                        app:icon=\"@drawable/ic_baseline_arrow_forward_24\"\n                        app:iconGravity=\"end\" />\n\n                </FrameLayout>\n\n                <androidx.appcompat.widget.Toolbar\n                    android:id=\"@+id/toolbar\"\n                    android:layout_width=\"match_parent\"\n                    android:layout_height=\"?attr/actionBarSize\"\n                    app:layout_collapseMode=\"pin\"\n                    app:menu=\"@menu/menu_preview_toolbar\">\n\n                </androidx.appcompat.widget.Toolbar>\n\n            </com.google.android.material.appbar.CollapsingToolbarLayout>\n\n            <LinearLayout\n                android:id=\"@+id/ll_tour\"\n                android:layout_width=\"match_parent\"\n                android:layout_height=\"wrap_content\"\n                app:layout_scrollFlags=\"scroll|exitUntilCollapsed\">\n\n                <androidx.recyclerview.widget.RecyclerView\n                    android:id=\"@+id/rv_tour_simplified\"\n                    android:layout_width=\"match_parent\"\n                    android:layout_height=\"wrap_content\"\n                    android:layout_marginTop=\"8dp\" />\n\n            </LinearLayout>\n\n        </com.google.android.material.appbar.AppBarLayout>\n\n        <androidx.core.widget.NestedScrollView\n            android:id=\"@+id/nsv_preview\"\n            android:layout_width=\"match_parent\"\n            android:layout_height=\"wrap_content\"\n            android:fillViewport=\"true\"\n            app:layout_behavior=\"@string/appbar_scrolling_view_behavior\">\n\n            <androidx.viewpager2.widget.ViewPager2\n                android:id=\"@+id/vp_news\"\n                android:layout_width=\"match_parent\"\n                android:layout_height=\"match_parent\" />\n\n        </androidx.core.widget.NestedScrollView>\n\n\n    </androidx.coordinatorlayout.widget.CoordinatorLayout>\n\n</layout>"
  },
  {
    "path": "app/src/main/res/layout/activity_preview_comment.xml",
    "content": "<?xml version=\"1.0\" encoding=\"utf-8\"?>\n<layout xmlns:android=\"http://schemas.android.com/apk/res/android\"\n    xmlns:app=\"http://schemas.android.com/apk/res-auto\">\n\n    <data>\n\n    </data>\n\n    <androidx.coordinatorlayout.widget.CoordinatorLayout\n        android:layout_width=\"match_parent\"\n        android:layout_height=\"match_parent\"\n        android:fitsSystemWindows=\"true\">\n\n        <com.google.android.material.appbar.AppBarLayout\n            android:layout_width=\"match_parent\"\n            android:layout_height=\"wrap_content\"\n            android:fitsSystemWindows=\"true\">\n\n            <androidx.appcompat.widget.Toolbar\n                android:id=\"@+id/toolbar\"\n                android:layout_width=\"match_parent\"\n                android:layout_height=\"?attr/actionBarSize\">\n\n            </androidx.appcompat.widget.Toolbar>\n\n        </com.google.android.material.appbar.AppBarLayout>\n\n        <androidx.fragment.app.FragmentContainerView\n            android:id=\"@+id/fcv_pre_comment\"\n            android:layout_width=\"match_parent\"\n            android:layout_height=\"match_parent\"\n            app:layout_behavior=\"@string/appbar_scrolling_view_behavior\" />\n\n    </androidx.coordinatorlayout.widget.CoordinatorLayout>\n</layout>"
  },
  {
    "path": "app/src/main/res/layout/activity_search.xml",
    "content": "<?xml version=\"1.0\" encoding=\"utf-8\"?>\n<layout xmlns:android=\"http://schemas.android.com/apk/res/android\">\n\n    <data>\n\n    </data>\n\n    <androidx.coordinatorlayout.widget.CoordinatorLayout\n        android:id=\"@+id/coordinator\"\n        android:layout_width=\"match_parent\"\n        android:layout_height=\"match_parent\">\n\n        <com.yenaly.han1meviewer.ui.view.HanimeSearchBar\n            android:id=\"@+id/search_bar\"\n            android:layout_width=\"match_parent\"\n            android:layout_height=\"wrap_content\"\n            android:layout_marginTop=\"8dp\"\n            android:elevation=\"16dp\" />\n\n\n        <com.scwang.smart.refresh.layout.SmartRefreshLayout\n            android:id=\"@+id/search_srl\"\n            android:layout_width=\"match_parent\"\n            android:layout_height=\"match_parent\"\n            android:clipToPadding=\"false\"\n            android:paddingHorizontal=\"8dp\"\n            android:paddingBottom=\"8dp\">\n\n            <com.scwang.smart.refresh.header.MaterialHeader\n                android:id=\"@+id/search_header\"\n                android:layout_width=\"match_parent\"\n                android:layout_height=\"wrap_content\"\n                android:layout_marginTop=\"68dp\" />\n\n            <com.drake.statelayout.StateLayout\n                android:id=\"@+id/state\"\n                android:layout_width=\"match_parent\"\n                android:layout_height=\"match_parent\">\n\n                <androidx.recyclerview.widget.RecyclerView\n                    android:id=\"@+id/search_rv\"\n                    android:layout_width=\"match_parent\"\n                    android:layout_height=\"match_parent\"\n                    android:clipToPadding=\"false\"\n                    android:paddingTop=\"68dp\" />\n\n            </com.drake.statelayout.StateLayout>\n\n            <com.scwang.smart.refresh.footer.ClassicsFooter\n                android:layout_width=\"match_parent\"\n                android:layout_height=\"wrap_content\" />\n\n        </com.scwang.smart.refresh.layout.SmartRefreshLayout>\n\n    </androidx.coordinatorlayout.widget.CoordinatorLayout>\n\n</layout>"
  },
  {
    "path": "app/src/main/res/layout/activity_settings.xml",
    "content": "<?xml version=\"1.0\" encoding=\"utf-8\"?>\n<layout xmlns:android=\"http://schemas.android.com/apk/res/android\"\n    xmlns:app=\"http://schemas.android.com/apk/res-auto\">\n\n    <data>\n\n    </data>\n\n    <androidx.coordinatorlayout.widget.CoordinatorLayout\n        android:layout_width=\"match_parent\"\n        android:layout_height=\"match_parent\"\n        android:fitsSystemWindows=\"true\">\n\n        <com.google.android.material.appbar.AppBarLayout\n            android:layout_width=\"match_parent\"\n            android:layout_height=\"wrap_content\"\n            android:fitsSystemWindows=\"true\">\n\n            <androidx.appcompat.widget.Toolbar\n                android:id=\"@+id/toolbar\"\n                android:layout_width=\"match_parent\"\n                android:layout_height=\"?attr/actionBarSize\">\n\n            </androidx.appcompat.widget.Toolbar>\n\n        </com.google.android.material.appbar.AppBarLayout>\n\n        <androidx.fragment.app.FragmentContainerView\n            android:id=\"@+id/fcv_settings\"\n            android:name=\"androidx.navigation.fragment.NavHostFragment\"\n            android:layout_width=\"match_parent\"\n            android:layout_height=\"match_parent\"\n            app:defaultNavHost=\"true\"\n            app:layout_behavior=\"@string/appbar_scrolling_view_behavior\"\n            app:navGraph=\"@navigation/nav_settings\" />\n\n    </androidx.coordinatorlayout.widget.CoordinatorLayout>\n</layout>"
  },
  {
    "path": "app/src/main/res/layout/activity_video.xml",
    "content": "<?xml version=\"1.0\" encoding=\"utf-8\"?>\n<layout xmlns:android=\"http://schemas.android.com/apk/res/android\">\n\n    <data>\n\n    </data>\n\n    <LinearLayout\n        android:layout_width=\"match_parent\"\n        android:layout_height=\"match_parent\"\n        android:orientation=\"vertical\">\n\n        <com.yenaly.han1meviewer.ui.view.video.HJzvdStd\n            android:id=\"@+id/video_player\"\n            android:layout_width=\"match_parent\"\n            android:layout_height=\"250dp\" />\n\n        <com.google.android.material.tabs.TabLayout\n            android:id=\"@+id/video_tl\"\n            android:layout_width=\"match_parent\"\n            android:layout_height=\"wrap_content\" />\n\n        <androidx.viewpager2.widget.ViewPager2\n            android:id=\"@+id/video_vp\"\n            android:layout_width=\"match_parent\"\n            android:layout_height=\"match_parent\" />\n\n    </LinearLayout>\n\n</layout>"
  },
  {
    "path": "app/src/main/res/layout/base_column_title.xml",
    "content": "<?xml version=\"1.0\" encoding=\"utf-8\"?>\n<layout xmlns:android=\"http://schemas.android.com/apk/res/android\"\n    xmlns:tools=\"http://schemas.android.com/tools\">\n\n    <data>\n\n    </data>\n\n    <merge\n        android:layout_width=\"match_parent\"\n        android:layout_height=\"wrap_content\"\n        tools:orientation=\"vertical\"\n        tools:parentTag=\"LinearLayout\">\n\n        <TextView\n            android:id=\"@+id/sub_title\"\n            android:layout_width=\"wrap_content\"\n            android:layout_height=\"wrap_content\"\n            android:textAppearance=\"@style/TextAppearance.Material3.TitleSmall\"\n            android:textColor=\"#FF8E9194\"\n            tools:text=\"H动漫\" />\n\n        <TextView\n            android:id=\"@+id/title\"\n            android:layout_width=\"wrap_content\"\n            android:layout_height=\"wrap_content\"\n            android:textAppearance=\"@style/TextAppearance.Material3.TitleLarge\"\n            tools:text=\"最新里番\" />\n\n    </merge>\n\n</layout>"
  },
  {
    "path": "app/src/main/res/layout/dialog_apply_deep_links.xml",
    "content": "<?xml version=\"1.0\" encoding=\"utf-8\"?>\n<ScrollView 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\n    <LinearLayout\n        android:layout_width=\"match_parent\"\n        android:layout_height=\"wrap_content\"\n        android:orientation=\"vertical\"\n        android:padding=\"?android:dialogPreferredPadding\">\n\n        <TextView\n            android:layout_width=\"match_parent\"\n            android:layout_height=\"wrap_content\"\n            android:text=\"@string/apply_deep_links_summary\" />\n\n        <TextView\n            android:layout_width=\"match_parent\"\n            android:layout_height=\"wrap_content\"\n            android:text=\"@string/apply_deep_links_tips\" />\n\n        <com.google.android.material.imageview.ShapeableImageView\n            android:layout_width=\"match_parent\"\n            android:layout_height=\"wrap_content\"\n            android:layout_marginTop=\"12dp\"\n            android:adjustViewBounds=\"true\"\n            app:shapeAppearanceOverlay=\"@style/RoundCornerImageView\"\n            app:srcCompat=\"@drawable/apply_deep_links\" />\n\n    </LinearLayout>\n\n</ScrollView>"
  },
  {
    "path": "app/src/main/res/layout/dialog_login.xml",
    "content": "<?xml version=\"1.0\" encoding=\"utf-8\"?>\n<LinearLayout 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=\"wrap_content\"\n    android:orientation=\"vertical\"\n    android:paddingHorizontal=\"?android:dialogPreferredPadding\"\n    android:paddingTop=\"8dp\">\n\n    <TextView\n        android:layout_width=\"match_parent\"\n        android:layout_height=\"wrap_content\"\n        android:layout_marginBottom=\"6dp\"\n        android:text=\"@string/login_tips\" />\n\n    <com.google.android.material.textfield.TextInputLayout\n        android:layout_width=\"match_parent\"\n        android:layout_height=\"wrap_content\">\n\n        <com.google.android.material.textfield.TextInputEditText\n            android:id=\"@+id/et_username\"\n            style=\"@style/ThemeOverlay.Material3.TextInputEditText.OutlinedBox\"\n            android:layout_width=\"match_parent\"\n            android:layout_height=\"wrap_content\"\n            android:hint=\"@string/email\"\n            android:inputType=\"textEmailAddress\"\n            android:maxLines=\"1\"\n            android:singleLine=\"true\" />\n\n    </com.google.android.material.textfield.TextInputLayout>\n\n    <com.google.android.material.textfield.TextInputLayout\n        android:layout_width=\"match_parent\"\n        android:layout_height=\"wrap_content\"\n        app:passwordToggleEnabled=\"true\">\n\n        <com.google.android.material.textfield.TextInputEditText\n            android:id=\"@+id/et_password\"\n            style=\"@style/ThemeOverlay.Material3.TextInputEditText.OutlinedBox\"\n            android:layout_width=\"match_parent\"\n            android:layout_height=\"wrap_content\"\n            android:hint=\"@string/password\"\n            android:inputType=\"textPassword\"\n            android:maxLines=\"1\"\n            android:singleLine=\"true\" />\n\n    </com.google.android.material.textfield.TextInputLayout>\n</LinearLayout>"
  },
  {
    "path": "app/src/main/res/layout/dialog_modify_h_keyframe.xml",
    "content": "<?xml version=\"1.0\" encoding=\"utf-8\"?>\n<LinearLayout xmlns:android=\"http://schemas.android.com/apk/res/android\"\n    android:layout_width=\"match_parent\"\n    android:layout_height=\"wrap_content\"\n    android:orientation=\"vertical\"\n    android:paddingHorizontal=\"?android:dialogPreferredPadding\"\n    android:paddingTop=\"8dp\">\n\n    <com.google.android.material.textfield.TextInputLayout\n        android:layout_width=\"match_parent\"\n        android:layout_height=\"wrap_content\">\n\n        <com.google.android.material.textfield.TextInputEditText\n            android:id=\"@+id/et_position\"\n            style=\"@style/ThemeOverlay.Material3.TextInputEditText.OutlinedBox\"\n            android:layout_width=\"match_parent\"\n            android:layout_height=\"wrap_content\"\n            android:hint=\"@string/position_ms\"\n            android:inputType=\"number\"\n            android:maxLines=\"1\" />\n\n    </com.google.android.material.textfield.TextInputLayout>\n\n    <com.google.android.material.textfield.TextInputLayout\n        android:layout_width=\"match_parent\"\n        android:layout_height=\"wrap_content\">\n\n        <com.google.android.material.textfield.TextInputEditText\n            android:id=\"@+id/et_prompt\"\n            style=\"@style/ThemeOverlay.Material3.TextInputEditText.OutlinedBox\"\n            android:layout_width=\"match_parent\"\n            android:layout_height=\"wrap_content\"\n            android:hint=\"@string/prompt\"\n            android:inputType=\"text\"\n            android:maxLines=\"2\" />\n\n    </com.google.android.material.textfield.TextInputLayout>\n</LinearLayout>"
  },
  {
    "path": "app/src/main/res/layout/dialog_modify_h_keyframes.xml",
    "content": "<?xml version=\"1.0\" encoding=\"utf-8\"?>\n<LinearLayout xmlns:android=\"http://schemas.android.com/apk/res/android\"\n    android:layout_width=\"match_parent\"\n    android:layout_height=\"wrap_content\"\n    android:orientation=\"vertical\"\n    android:paddingHorizontal=\"?android:dialogPreferredPadding\"\n    android:paddingTop=\"8dp\">\n\n    <com.google.android.material.textfield.TextInputLayout\n        android:layout_width=\"match_parent\"\n        android:layout_height=\"wrap_content\">\n\n        <com.google.android.material.textfield.TextInputEditText\n            android:id=\"@+id/et_title\"\n            style=\"@style/ThemeOverlay.Material3.TextInputEditText.OutlinedBox\"\n            android:layout_width=\"match_parent\"\n            android:layout_height=\"wrap_content\"\n            android:hint=\"@string/title\"\n            android:inputType=\"text\"\n            android:maxLines=\"2\" />\n\n    </com.google.android.material.textfield.TextInputLayout>\n\n    <com.google.android.material.textfield.TextInputLayout\n        android:layout_width=\"match_parent\"\n        android:layout_height=\"wrap_content\">\n\n        <com.google.android.material.textfield.TextInputEditText\n            android:id=\"@+id/et_video_code\"\n            style=\"@style/ThemeOverlay.Material3.TextInputEditText.OutlinedBox\"\n            android:layout_width=\"match_parent\"\n            android:layout_height=\"wrap_content\"\n            android:enabled=\"false\"\n            android:hint=\"@string/video_code\"\n            android:inputType=\"text\"\n            android:maxLines=\"1\" />\n\n    </com.google.android.material.textfield.TextInputLayout>\n</LinearLayout>"
  },
  {
    "path": "app/src/main/res/layout/dialog_playlist_modify_edit_text.xml",
    "content": "<?xml version=\"1.0\" encoding=\"utf-8\"?>\n<LinearLayout xmlns:android=\"http://schemas.android.com/apk/res/android\"\n    android:layout_width=\"match_parent\"\n    android:layout_height=\"wrap_content\"\n    android:orientation=\"vertical\"\n    android:paddingHorizontal=\"?android:dialogPreferredPadding\"\n    android:paddingTop=\"8dp\">\n\n    <com.google.android.material.textfield.TextInputLayout\n        android:layout_width=\"match_parent\"\n        android:layout_height=\"wrap_content\">\n\n        <com.google.android.material.textfield.TextInputEditText\n            android:id=\"@+id/et_title\"\n            style=\"@style/ThemeOverlay.Material3.TextInputEditText.OutlinedBox\"\n            android:layout_width=\"match_parent\"\n            android:layout_height=\"wrap_content\"\n            android:hint=\"@string/playlist_title\"\n            android:inputType=\"text\"\n            android:maxLines=\"3\" />\n\n    </com.google.android.material.textfield.TextInputLayout>\n\n    <com.google.android.material.textfield.TextInputLayout\n        android:layout_width=\"match_parent\"\n        android:layout_height=\"wrap_content\">\n\n        <com.google.android.material.textfield.TextInputEditText\n            android:id=\"@+id/et_desc\"\n            style=\"@style/ThemeOverlay.Material3.TextInputEditText.OutlinedBox\"\n            android:layout_width=\"match_parent\"\n            android:layout_height=\"wrap_content\"\n            android:hint=\"@string/playlist_description\"\n            android:inputType=\"text\"\n            android:maxLines=\"3\" />\n\n    </com.google.android.material.textfield.TextInputLayout>\n</LinearLayout>"
  },
  {
    "path": "app/src/main/res/layout/dialog_proxy.xml",
    "content": "<?xml version=\"1.0\" encoding=\"utf-8\"?>\n<LinearLayout 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=\"wrap_content\"\n    android:orientation=\"vertical\"\n    android:paddingHorizontal=\"?android:dialogPreferredPadding\"\n    android:paddingTop=\"8dp\">\n\n    <HorizontalScrollView\n        android:layout_width=\"match_parent\"\n        android:layout_height=\"wrap_content\">\n\n        <com.google.android.material.chip.ChipGroup\n            android:id=\"@+id/cg_types\"\n            android:layout_width=\"wrap_content\"\n            android:layout_height=\"wrap_content\"\n            app:selectionRequired=\"true\"\n            app:singleLine=\"true\"\n            app:singleSelection=\"true\">\n\n            <com.google.android.material.chip.Chip\n                android:id=\"@+id/chip_direct\"\n                style=\"@style/Widget.Material3.Chip.Filter.Elevated\"\n                android:layout_width=\"wrap_content\"\n                android:layout_height=\"wrap_content\"\n                android:text=\"@string/direct\" />\n\n            <com.google.android.material.chip.Chip\n                android:id=\"@+id/chip_system_proxy\"\n                style=\"@style/Widget.Material3.Chip.Filter.Elevated\"\n                android:layout_width=\"wrap_content\"\n                android:layout_height=\"wrap_content\"\n                android:text=\"@string/system_proxy\" />\n\n            <com.google.android.material.chip.Chip\n                android:id=\"@+id/chip_http\"\n                style=\"@style/Widget.Material3.Chip.Filter.Elevated\"\n                android:layout_width=\"wrap_content\"\n                android:layout_height=\"wrap_content\"\n                android:text=\"@string/http\" />\n\n            <com.google.android.material.chip.Chip\n                android:id=\"@+id/chip_socks\"\n                style=\"@style/Widget.Material3.Chip.Filter.Elevated\"\n                android:layout_width=\"wrap_content\"\n                android:layout_height=\"wrap_content\"\n                android:text=\"@string/socks\" />\n\n        </com.google.android.material.chip.ChipGroup>\n\n    </HorizontalScrollView>\n\n    <com.google.android.material.textfield.TextInputLayout\n        android:layout_width=\"match_parent\"\n        android:layout_height=\"wrap_content\">\n\n        <com.google.android.material.textfield.TextInputEditText\n            android:id=\"@+id/et_ip\"\n            style=\"@style/ThemeOverlay.Material3.TextInputEditText.OutlinedBox\"\n            android:layout_width=\"match_parent\"\n            android:layout_height=\"wrap_content\"\n            android:hint=\"@string/host_or_ipv4\"\n            android:inputType=\"text\"\n            android:maxLines=\"1\"\n            android:singleLine=\"true\" />\n\n    </com.google.android.material.textfield.TextInputLayout>\n\n    <com.google.android.material.textfield.TextInputLayout\n        android:layout_width=\"match_parent\"\n        android:layout_height=\"wrap_content\">\n\n        <com.google.android.material.textfield.TextInputEditText\n            android:id=\"@+id/et_port\"\n            style=\"@style/ThemeOverlay.Material3.TextInputEditText.OutlinedBox\"\n            android:layout_width=\"match_parent\"\n            android:layout_height=\"wrap_content\"\n            android:hint=\"@string/port\"\n            android:inputType=\"numberSigned\"\n            android:maxLength=\"5\"\n            android:maxLines=\"1\"\n            android:singleLine=\"true\" />\n\n    </com.google.android.material.textfield.TextInputLayout>\n</LinearLayout>"
  },
  {
    "path": "app/src/main/res/layout/fragment_comment.xml",
    "content": "<?xml version=\"1.0\" encoding=\"utf-8\"?>\n<layout xmlns:android=\"http://schemas.android.com/apk/res/android\"\n    xmlns:app=\"http://schemas.android.com/apk/res-auto\">\n\n    <data>\n\n    </data>\n\n    <androidx.coordinatorlayout.widget.CoordinatorLayout\n        android:layout_width=\"match_parent\"\n        android:layout_height=\"match_parent\">\n\n        <com.scwang.smart.refresh.layout.SmartRefreshLayout\n            android:id=\"@+id/srl_comment\"\n            android:layout_width=\"match_parent\"\n            android:layout_height=\"match_parent\"\n            app:srlEnableLoadMore=\"false\">\n\n            <com.scwang.smart.refresh.header.MaterialHeader\n                android:layout_width=\"match_parent\"\n                android:layout_height=\"wrap_content\" />\n\n            <com.drake.statelayout.StateLayout\n                android:id=\"@+id/state\"\n                android:layout_width=\"match_parent\"\n                android:layout_height=\"match_parent\">\n\n                <androidx.recyclerview.widget.RecyclerView\n                    android:id=\"@+id/rv_comment\"\n                    android:layout_width=\"match_parent\"\n                    android:layout_height=\"match_parent\"\n                    android:clipToPadding=\"false\"\n                    android:padding=\"8dp\" />\n\n            </com.drake.statelayout.StateLayout>\n\n        </com.scwang.smart.refresh.layout.SmartRefreshLayout>\n\n        <com.google.android.material.floatingactionbutton.FloatingActionButton\n            android:id=\"@+id/btn_comment\"\n            android:layout_width=\"wrap_content\"\n            android:layout_height=\"wrap_content\"\n            android:layout_gravity=\"end|bottom\"\n            android:layout_margin=\"16dp\"\n            android:contentDescription=\"@null\"\n            android:src=\"@drawable/ic_baseline_reply_24\"\n            app:layout_behavior=\"@string/hide_bottom_view_on_scroll_behavior\" />\n\n    </androidx.coordinatorlayout.widget.CoordinatorLayout>\n\n</layout>"
  },
  {
    "path": "app/src/main/res/layout/fragment_h_keyframes.xml",
    "content": "<?xml version=\"1.0\" encoding=\"utf-8\"?>\n<layout xmlns:android=\"http://schemas.android.com/apk/res/android\"\n    xmlns:app=\"http://schemas.android.com/apk/res-auto\">\n\n    <data>\n\n    </data>\n\n    <androidx.coordinatorlayout.widget.CoordinatorLayout\n        android:layout_width=\"match_parent\"\n        android:layout_height=\"match_parent\">\n\n        <androidx.recyclerview.widget.RecyclerView\n            android:id=\"@+id/rv_keyframe\"\n            android:layout_width=\"match_parent\"\n            android:layout_height=\"match_parent\"\n            android:clipToPadding=\"false\" />\n\n        <com.google.android.material.floatingactionbutton.FloatingActionButton\n            android:id=\"@+id/btn_up\"\n            android:layout_width=\"wrap_content\"\n            android:layout_height=\"wrap_content\"\n            android:layout_gravity=\"top|end\"\n            android:layout_marginBottom=\"80dp\"\n            android:contentDescription=\"@null\"\n            android:src=\"@drawable/baseline_keyboard_arrow_up_24\"\n            app:fabSize=\"mini\"\n            app:layout_anchor=\"@id/btn_down\"\n            app:layout_anchorGravity=\"top\"\n            app:layout_behavior=\"@string/hide_bottom_view_on_scroll_behavior\" />\n\n        <com.google.android.material.floatingactionbutton.FloatingActionButton\n            android:id=\"@+id/btn_down\"\n            android:layout_width=\"wrap_content\"\n            android:layout_height=\"wrap_content\"\n            android:layout_gravity=\"end|bottom\"\n            android:layout_margin=\"16dp\"\n            android:contentDescription=\"@null\"\n            android:src=\"@drawable/baseline_keyboard_arrow_down_24\"\n            app:fabSize=\"mini\"\n            app:layout_behavior=\"@string/hide_bottom_view_on_scroll_behavior\" />\n\n    </androidx.coordinatorlayout.widget.CoordinatorLayout>\n</layout>"
  },
  {
    "path": "app/src/main/res/layout/fragment_home_page.xml",
    "content": "<?xml version=\"1.0\" encoding=\"utf-8\"?>\n<layout 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\n    <data>\n\n    </data>\n\n    <androidx.coordinatorlayout.widget.CoordinatorLayout\n        android:id=\"@+id/coordinator\"\n        android:layout_width=\"match_parent\"\n        android:layout_height=\"match_parent\"\n        android:fitsSystemWindows=\"true\">\n\n        <com.google.android.material.appbar.AppBarLayout\n            android:id=\"@+id/app_bar\"\n            android:layout_width=\"match_parent\"\n            android:layout_height=\"wrap_content\"\n            android:fitsSystemWindows=\"true\">\n\n            <com.google.android.material.appbar.CollapsingToolbarLayout\n                android:id=\"@+id/collapsing_toolbar\"\n                android:layout_width=\"match_parent\"\n                android:layout_height=\"240dp\"\n                android:fitsSystemWindows=\"true\"\n                app:layout_scrollFlags=\"exitUntilCollapsed|scroll\"\n                app:titleEnabled=\"false\">\n\n                <androidx.constraintlayout.widget.ConstraintLayout\n                    android:layout_width=\"match_parent\"\n                    android:layout_height=\"match_parent\"\n                    android:fitsSystemWindows=\"true\">\n\n                    <ImageView\n                        android:id=\"@+id/cover\"\n                        android:layout_width=\"match_parent\"\n                        android:layout_height=\"match_parent\"\n                        android:contentDescription=\"@null\"\n                        android:fitsSystemWindows=\"true\"\n                        android:scaleType=\"centerCrop\"\n                        app:layout_collapseMode=\"parallax\" />\n\n                    <View\n                        android:layout_width=\"match_parent\"\n                        android:layout_height=\"match_parent\"\n                        android:background=\"@color/per60_transparent_black\" />\n\n                    <LinearLayout\n                        android:id=\"@+id/banner\"\n                        android:layout_width=\"match_parent\"\n                        android:layout_height=\"wrap_content\"\n                        android:baselineAligned=\"false\"\n                        android:gravity=\"bottom\"\n                        android:orientation=\"horizontal\"\n                        android:paddingBottom=\"8dp\"\n                        android:visibility=\"gone\"\n                        app:layout_constraintBottom_toBottomOf=\"parent\"\n                        tools:visibility=\"visible\">\n\n                        <LinearLayout\n                            android:layout_width=\"0dp\"\n                            android:layout_height=\"wrap_content\"\n                            android:layout_weight=\"1\"\n                            android:orientation=\"vertical\">\n\n                            <LinearLayout\n                                android:layout_width=\"match_parent\"\n                                android:layout_height=\"wrap_content\">\n\n                                <View\n                                    android:id=\"@+id/a_color\"\n                                    android:layout_width=\"8dp\"\n                                    android:layout_height=\"match_parent\"\n                                    android:layout_marginVertical=\"4dp\"\n                                    android:background=\"@color/red\" />\n\n                                <TextView\n                                    android:id=\"@+id/tv_banner_title\"\n                                    style=\"@style/TextAppearance.Material3.TitleLarge\"\n                                    android:layout_width=\"wrap_content\"\n                                    android:layout_height=\"wrap_content\"\n                                    android:layout_marginStart=\"4dp\"\n                                    android:ellipsize=\"end\"\n                                    android:maxLines=\"1\"\n                                    android:paddingVertical=\"4dp\"\n                                    android:textAlignment=\"center\"\n                                    android:textColor=\"@color/white\"\n                                    android:textStyle=\"bold\"\n                                    tools:text=\"1234 -v 125\" />\n\n                            </LinearLayout>\n\n                            <TextView\n                                android:id=\"@+id/tv_banner_desc\"\n                                android:layout_width=\"wrap_content\"\n                                android:layout_height=\"wrap_content\"\n                                android:layout_marginStart=\"10dp\"\n                                android:layout_marginTop=\"2dp\"\n                                android:ellipsize=\"end\"\n                                android:maxLines=\"2\"\n                                android:padding=\"4dp\"\n                                tools:text=\"131231313566666666666624444444444444444466666\" />\n\n                        </LinearLayout>\n\n                        <FrameLayout\n                            android:id=\"@+id/btn_banner\"\n                            android:layout_width=\"64dp\"\n                            android:layout_height=\"40dp\"\n                            android:background=\"@android:color/transparent\">\n\n                            <ImageView\n                                android:id=\"@+id/iv_banner\"\n                                android:layout_width=\"wrap_content\"\n                                android:layout_height=\"wrap_content\"\n                                android:layout_gravity=\"center|end\"\n                                android:layout_marginEnd=\"4dp\"\n                                android:background=\"@drawable/ic_baseline_arrow_forward_24\"\n                                android:backgroundTint=\"@color/red\"\n                                android:contentDescription=\"@string/enter\" />\n\n                        </FrameLayout>\n                        <!--\n\n                        <com.google.android.material.button.MaterialButton\n                            android:id=\"@+id/btn_banner\"\n                            style=\"@style/Widget.Material3.Button.IconButton\"\n                            android:layout_width=\"wrap_content\"\n                            android:layout_height=\"wrap_content\"\n                            android:layout_marginHorizontal=\"8dp\"\n                            android:backgroundTint=\"@color/per70_transparent_white\"\n                            app:icon=\"@drawable/ic_baseline_arrow_forward_24\"\n                            app:iconTint=\"@color/red\" />\n                            -->\n\n                    </LinearLayout>\n\n                </androidx.constraintlayout.widget.ConstraintLayout>\n\n                <androidx.appcompat.widget.Toolbar\n                    android:id=\"@+id/toolbar\"\n                    android:layout_width=\"match_parent\"\n                    android:layout_height=\"?attr/actionBarSize\"\n                    app:layout_collapseMode=\"pin\"\n                    app:menu=\"@menu/menu_main_toolbar\">\n\n                </androidx.appcompat.widget.Toolbar>\n\n            </com.google.android.material.appbar.CollapsingToolbarLayout>\n\n        </com.google.android.material.appbar.AppBarLayout>\n\n        <com.scwang.smart.refresh.layout.SmartRefreshLayout\n            android:id=\"@+id/home_page_srl\"\n            android:layout_width=\"match_parent\"\n            android:layout_height=\"match_parent\"\n            app:layout_behavior=\"@string/appbar_scrolling_view_behavior\">\n\n            <com.scwang.smart.refresh.header.MaterialHeader\n                android:id=\"@+id/header\"\n                android:layout_width=\"match_parent\"\n                android:layout_height=\"wrap_content\" />\n\n            <com.drake.statelayout.StateLayout\n                android:id=\"@+id/state\"\n                android:layout_width=\"match_parent\"\n                android:layout_height=\"match_parent\">\n\n                <com.yenaly.yenaly_libs.base.view.RecyclerViewAtViewPager2\n                    android:id=\"@+id/rv\"\n                    android:layout_width=\"match_parent\"\n                    android:layout_height=\"match_parent\"\n                    app:layoutManager=\"androidx.recyclerview.widget.LinearLayoutManager\" />\n\n            </com.drake.statelayout.StateLayout>\n\n        </com.scwang.smart.refresh.layout.SmartRefreshLayout>\n\n    </androidx.coordinatorlayout.widget.CoordinatorLayout>\n\n</layout>"
  },
  {
    "path": "app/src/main/res/layout/fragment_list_only.xml",
    "content": "<?xml version=\"1.0\" encoding=\"utf-8\"?>\n<layout xmlns:android=\"http://schemas.android.com/apk/res/android\">\n\n    <data>\n\n    </data>\n\n    <FrameLayout\n        android:layout_width=\"match_parent\"\n        android:layout_height=\"match_parent\">\n\n        <androidx.recyclerview.widget.RecyclerView\n            android:id=\"@+id/rv_list\"\n            android:layout_width=\"match_parent\"\n            android:layout_height=\"match_parent\"\n            android:clipToPadding=\"false\"\n            android:padding=\"8dp\" />\n\n    </FrameLayout>\n</layout>"
  },
  {
    "path": "app/src/main/res/layout/fragment_page_list.xml",
    "content": "<?xml version=\"1.0\" encoding=\"utf-8\"?>\n<layout xmlns:android=\"http://schemas.android.com/apk/res/android\"\n    xmlns:app=\"http://schemas.android.com/apk/res-auto\">\n\n    <data>\n\n    </data>\n\n    <androidx.coordinatorlayout.widget.CoordinatorLayout\n        android:id=\"@+id/coordinator\"\n        android:layout_width=\"match_parent\"\n        android:layout_height=\"match_parent\"\n        android:fitsSystemWindows=\"true\">\n\n        <com.google.android.material.appbar.AppBarLayout\n            android:layout_width=\"match_parent\"\n            android:layout_height=\"wrap_content\"\n            android:fitsSystemWindows=\"true\">\n\n            <androidx.appcompat.widget.Toolbar\n                android:id=\"@+id/toolbar\"\n                android:layout_width=\"match_parent\"\n                android:layout_height=\"?attr/actionBarSize\">\n\n            </androidx.appcompat.widget.Toolbar>\n\n        </com.google.android.material.appbar.AppBarLayout>\n\n        <com.scwang.smart.refresh.layout.SmartRefreshLayout\n            android:id=\"@+id/srl_page_list\"\n            android:layout_width=\"match_parent\"\n            android:layout_height=\"match_parent\"\n            app:layout_behavior=\"@string/appbar_scrolling_view_behavior\">\n\n            <com.scwang.smart.refresh.header.MaterialHeader\n                android:layout_width=\"match_parent\"\n                android:layout_height=\"wrap_content\" />\n\n\n            <com.drake.statelayout.StateLayout\n                android:layout_width=\"match_parent\"\n                android:layout_height=\"match_parent\"\n                android:id=\"@+id/state\">\n\n                <androidx.recyclerview.widget.RecyclerView\n                    android:id=\"@+id/rv_page_list\"\n                    android:layout_width=\"match_parent\"\n                    android:layout_height=\"match_parent\"\n                    android:clipToPadding=\"false\"\n                    android:padding=\"8dp\" />\n\n            </com.drake.statelayout.StateLayout>\n\n            <com.scwang.smart.refresh.footer.ClassicsFooter\n                android:layout_width=\"match_parent\"\n                android:layout_height=\"wrap_content\" />\n\n        </com.scwang.smart.refresh.layout.SmartRefreshLayout>\n\n    </androidx.coordinatorlayout.widget.CoordinatorLayout>\n\n</layout>"
  },
  {
    "path": "app/src/main/res/layout/fragment_playlist.xml",
    "content": "<?xml version=\"1.0\" encoding=\"utf-8\"?>\n<layout 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\n    <data>\n\n    </data>\n\n    <androidx.drawerlayout.widget.DrawerLayout\n        android:id=\"@+id/dl_playlist\"\n        android:layout_width=\"match_parent\"\n        android:layout_height=\"match_parent\">\n\n        <androidx.coordinatorlayout.widget.CoordinatorLayout\n            android:id=\"@+id/coordinator\"\n            android:layout_width=\"match_parent\"\n            android:layout_height=\"match_parent\"\n            android:fitsSystemWindows=\"true\">\n\n            <com.google.android.material.appbar.AppBarLayout\n                android:id=\"@+id/app_bar\"\n                android:layout_width=\"match_parent\"\n                android:layout_height=\"wrap_content\"\n                android:fitsSystemWindows=\"true\">\n\n                <com.google.android.material.appbar.CollapsingToolbarLayout\n                    android:layout_width=\"match_parent\"\n                    android:layout_height=\"240dp\"\n                    android:fitsSystemWindows=\"true\"\n                    app:layout_scrollFlags=\"exitUntilCollapsed|scroll\"\n                    app:titleEnabled=\"false\">\n\n                    <androidx.constraintlayout.widget.ConstraintLayout\n                        android:layout_width=\"match_parent\"\n                        android:layout_height=\"match_parent\"\n                        android:fitsSystemWindows=\"true\">\n\n                        <ImageView\n                            android:id=\"@+id/cover\"\n                            android:layout_width=\"match_parent\"\n                            android:layout_height=\"match_parent\"\n                            android:contentDescription=\"@null\"\n                            android:fitsSystemWindows=\"true\"\n                            android:scaleType=\"centerCrop\"\n                            app:layout_collapseMode=\"parallax\" />\n\n                        <View\n                            android:layout_width=\"match_parent\"\n                            android:layout_height=\"match_parent\"\n                            android:background=\"@color/per70_transparent_black\" />\n\n                        <com.yenaly.han1meviewer.ui.view.PlaylistHeader\n                            android:id=\"@+id/playlist_header\"\n                            android:layout_width=\"match_parent\"\n                            android:layout_height=\"wrap_content\"\n                            android:layout_marginBottom=\"8dp\"\n                            android:visibility=\"gone\"\n                            app:layout_constraintBottom_toBottomOf=\"parent\"\n                            tools:visibility=\"visible\" />\n\n                    </androidx.constraintlayout.widget.ConstraintLayout>\n\n                    <androidx.appcompat.widget.Toolbar\n                        android:id=\"@+id/toolbar\"\n                        android:layout_width=\"match_parent\"\n                        android:layout_height=\"?attr/actionBarSize\"\n                        app:layout_collapseMode=\"pin\"\n                        app:menu=\"@menu/menu_playlist_toolbar\">\n\n                    </androidx.appcompat.widget.Toolbar>\n\n                </com.google.android.material.appbar.CollapsingToolbarLayout>\n\n            </com.google.android.material.appbar.AppBarLayout>\n\n            <androidx.coordinatorlayout.widget.CoordinatorLayout\n                android:layout_width=\"match_parent\"\n                android:layout_height=\"match_parent\"\n                app:layout_behavior=\"@string/appbar_scrolling_view_behavior\">\n\n                <com.scwang.smart.refresh.layout.SmartRefreshLayout\n                    android:id=\"@+id/srl_page_list\"\n                    android:layout_width=\"match_parent\"\n                    android:layout_height=\"match_parent\">\n\n                    <com.scwang.smart.refresh.header.MaterialHeader\n                        android:layout_width=\"match_parent\"\n                        android:layout_height=\"wrap_content\" />\n\n                    <com.drake.statelayout.StateLayout\n                        android:id=\"@+id/state_page_list\"\n                        android:layout_width=\"match_parent\"\n                        android:layout_height=\"match_parent\">\n\n                        <androidx.recyclerview.widget.RecyclerView\n                            android:id=\"@+id/rv_page_list\"\n                            android:layout_width=\"match_parent\"\n                            android:layout_height=\"match_parent\"\n                            android:clipToPadding=\"false\"\n                            android:paddingHorizontal=\"8dp\"\n                            android:paddingTop=\"16dp\"\n                            android:paddingBottom=\"8dp\" />\n\n\n                    </com.drake.statelayout.StateLayout>\n\n                    <com.scwang.smart.refresh.footer.ClassicsFooter\n                        android:layout_width=\"match_parent\"\n                        android:layout_height=\"wrap_content\" />\n\n                </com.scwang.smart.refresh.layout.SmartRefreshLayout>\n\n            </androidx.coordinatorlayout.widget.CoordinatorLayout>\n\n        </androidx.coordinatorlayout.widget.CoordinatorLayout>\n\n        <com.google.android.material.navigation.NavigationView\n            android:id=\"@+id/nv_playlist\"\n            android:layout_width=\"wrap_content\"\n            android:layout_height=\"match_parent\"\n            android:layout_gravity=\"end\"\n            android:fitsSystemWindows=\"true\">\n\n            <LinearLayout\n                android:layout_width=\"match_parent\"\n                android:layout_height=\"match_parent\"\n                android:orientation=\"vertical\"\n                android:padding=\"8dp\">\n\n                <LinearLayout\n                    android:layout_width=\"match_parent\"\n                    android:layout_height=\"wrap_content\"\n                    android:layout_marginBottom=\"16dp\"\n                    android:gravity=\"center\"\n                    android:orientation=\"horizontal\"\n                    android:paddingTop=\"48dp\">\n\n                    <TextView\n                        android:layout_width=\"0dp\"\n                        android:layout_height=\"wrap_content\"\n                        android:layout_weight=\"1\"\n                        android:gravity=\"start|bottom\"\n                        android:text=\"@string/play_list\"\n                        android:textSize=\"24sp\" />\n\n\n                    <com.google.android.material.button.MaterialButton\n                        android:id=\"@+id/btn_new_playlist\"\n                        style=\"@style/Widget.Material3.Button.IconButton\"\n                        android:layout_width=\"wrap_content\"\n                        android:layout_height=\"wrap_content\"\n                        android:insetLeft=\"0dp\"\n                        android:insetTop=\"0dp\"\n                        android:insetRight=\"0dp\"\n                        android:insetBottom=\"0dp\"\n                        android:minWidth=\"0dp\"\n                        android:minHeight=\"0dp\"\n                        app:icon=\"@drawable/baseline_add_24\" />\n\n                    <com.google.android.material.button.MaterialButton\n                        android:id=\"@+id/btn_refresh_playlists\"\n                        style=\"@style/Widget.Material3.Button.IconButton\"\n                        android:layout_width=\"wrap_content\"\n                        android:layout_height=\"wrap_content\"\n                        android:insetLeft=\"0dp\"\n                        android:insetTop=\"0dp\"\n                        android:insetRight=\"0dp\"\n                        android:insetBottom=\"0dp\"\n                        android:minWidth=\"0dp\"\n                        android:minHeight=\"0dp\"\n                        app:icon=\"@drawable/baseline_refresh_24\" />\n\n                </LinearLayout>\n\n                <com.drake.statelayout.StateLayout\n                    android:id=\"@+id/state_playlist\"\n                    android:layout_width=\"match_parent\"\n                    android:layout_height=\"match_parent\">\n\n                    <androidx.recyclerview.widget.RecyclerView\n                        android:id=\"@+id/rv_playlist\"\n                        android:layout_width=\"match_parent\"\n                        android:layout_height=\"match_parent\" />\n\n                </com.drake.statelayout.StateLayout>\n\n\n            </LinearLayout>\n\n\n        </com.google.android.material.navigation.NavigationView>\n\n    </androidx.drawerlayout.widget.DrawerLayout>\n\n</layout>"
  },
  {
    "path": "app/src/main/res/layout/fragment_tab_view_pager_only.xml",
    "content": "<?xml version=\"1.0\" encoding=\"utf-8\"?>\n<layout 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\n    <data>\n\n    </data>\n\n    <androidx.drawerlayout.widget.DrawerLayout\n        android:layout_width=\"match_parent\"\n        android:layout_height=\"match_parent\"\n        tools:openDrawer=\"end\">\n\n        <androidx.coordinatorlayout.widget.CoordinatorLayout\n            android:id=\"@+id/coordinator\"\n            android:layout_width=\"match_parent\"\n            android:layout_height=\"match_parent\"\n            android:fitsSystemWindows=\"true\">\n\n            <com.google.android.material.appbar.AppBarLayout\n                android:layout_width=\"match_parent\"\n                android:layout_height=\"wrap_content\"\n                android:fitsSystemWindows=\"true\">\n\n                <androidx.appcompat.widget.Toolbar\n                    android:id=\"@+id/toolbar\"\n                    android:layout_width=\"match_parent\"\n                    android:layout_height=\"?attr/actionBarSize\">\n\n                </androidx.appcompat.widget.Toolbar>\n\n                <com.google.android.material.tabs.TabLayout\n                    android:id=\"@+id/tab_layout\"\n                    android:layout_width=\"match_parent\"\n                    android:layout_height=\"wrap_content\" />\n\n            </com.google.android.material.appbar.AppBarLayout>\n\n\n            <androidx.viewpager2.widget.ViewPager2\n                android:id=\"@+id/view_pager\"\n                android:layout_width=\"match_parent\"\n                android:layout_height=\"match_parent\"\n                app:layout_behavior=\"@string/appbar_scrolling_view_behavior\" />\n\n\n        </androidx.coordinatorlayout.widget.CoordinatorLayout>\n\n        <com.google.android.material.navigation.NavigationView\n            android:id=\"@+id/nv_playlist\"\n            android:layout_width=\"wrap_content\"\n            android:layout_height=\"match_parent\"\n            android:layout_gravity=\"end\"\n            android:fitsSystemWindows=\"true\">\n\n            <LinearLayout\n                android:layout_width=\"match_parent\"\n                android:layout_height=\"match_parent\"\n                android:orientation=\"vertical\"\n                android:padding=\"8dp\">\n\n                <LinearLayout\n                    android:layout_width=\"match_parent\"\n                    android:layout_height=\"wrap_content\"\n                    android:layout_marginTop=\"16dp\"\n                    android:layout_marginBottom=\"16dp\"\n                    android:gravity=\"center\"\n                    android:orientation=\"horizontal\">\n\n                    <TextView\n                        android:id=\"@+id/tv_category\"\n                        android:layout_width=\"0dp\"\n                        android:layout_height=\"wrap_content\"\n                        android:layout_weight=\"1\"\n                        android:gravity=\"start|bottom\"\n                        android:text=\"@string/category\"\n                        android:textSize=\"24sp\" />\n\n\n                    <com.google.android.material.button.MaterialButton\n                        android:id=\"@+id/btn_new_playlist\"\n                        style=\"@style/Widget.Material3.Button.IconButton\"\n                        android:layout_width=\"wrap_content\"\n                        android:layout_height=\"wrap_content\"\n                        android:insetLeft=\"0dp\"\n                        android:insetTop=\"0dp\"\n                        android:insetRight=\"0dp\"\n                        android:insetBottom=\"0dp\"\n                        android:minWidth=\"0dp\"\n                        android:minHeight=\"0dp\"\n                        app:icon=\"@drawable/baseline_add_24\" />\n\n                </LinearLayout>\n\n                <com.drake.statelayout.StateLayout\n                    android:id=\"@+id/state_playlist\"\n                    android:layout_width=\"match_parent\"\n                    android:layout_height=\"match_parent\">\n\n                    <androidx.recyclerview.widget.RecyclerView\n                        android:id=\"@+id/rv_playlist\"\n                        android:layout_width=\"match_parent\"\n                        android:layout_height=\"match_parent\" />\n\n                </com.drake.statelayout.StateLayout>\n\n\n            </LinearLayout>\n\n\n        </com.google.android.material.navigation.NavigationView>\n\n    </androidx.drawerlayout.widget.DrawerLayout>\n\n</layout>"
  },
  {
    "path": "app/src/main/res/layout/fragment_tag_selector.xml",
    "content": "<?xml version=\"1.0\" encoding=\"utf-8\"?>\n<LinearLayout xmlns:android=\"http://schemas.android.com/apk/res/android\"\n    android:layout_width=\"match_parent\"\n    android:layout_height=\"match_parent\"\n    android:background=\"@drawable/shape_tag_selector\"\n    android:clipToPadding=\"false\"\n    android:orientation=\"vertical\"\n    android:paddingTop=\"16dp\"\n    android:paddingBottom=\"16dp\">\n\n    <View\n        android:layout_width=\"100dp\"\n        android:layout_height=\"3dp\"\n        android:layout_gravity=\"center\"\n        android:layout_marginBottom=\"16dp\"\n        android:background=\"@color/white\" />\n\n    <com.google.android.material.tabs.TabLayout\n        android:id=\"@+id/tag_tl\"\n        android:layout_width=\"match_parent\"\n        android:layout_height=\"wrap_content\" />\n\n    <androidx.viewpager2.widget.ViewPager2\n        android:layout_width=\"match_parent\"\n        android:layout_height=\"match_parent\" />\n\n</LinearLayout>"
  },
  {
    "path": "app/src/main/res/layout/fragment_video_introduction.xml",
    "content": "<?xml version=\"1.0\" encoding=\"utf-8\"?>\n<layout xmlns:android=\"http://schemas.android.com/apk/res/android\">\n\n    <data>\n\n    </data>\n\n\n    <androidx.recyclerview.widget.RecyclerView\n        android:id=\"@+id/rv_video_intro\"\n        android:layout_width=\"match_parent\"\n        android:layout_height=\"match_parent\" />\n\n\n</layout>"
  },
  {
    "path": "app/src/main/res/layout/item_h_keyframe.xml",
    "content": "<?xml version=\"1.0\" encoding=\"utf-8\"?>\n<LinearLayout 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:gravity=\"center\"\n    android:orientation=\"horizontal\">\n\n    <LinearLayout\n        android:layout_width=\"0dp\"\n        android:layout_height=\"wrap_content\"\n        android:layout_weight=\"1\"\n        android:orientation=\"vertical\">\n\n        <LinearLayout\n            android:layout_width=\"match_parent\"\n            android:layout_height=\"wrap_content\">\n\n            <TextView\n                android:id=\"@+id/tv_keyframe\"\n                android:layout_width=\"wrap_content\"\n                android:layout_height=\"wrap_content\"\n                android:ellipsize=\"end\"\n                android:maxLines=\"1\"\n                android:textSize=\"24sp\"\n                android:textStyle=\"bold\"\n                tools:text=\"12:31\" />\n\n            <TextView\n                android:id=\"@+id/tv_index\"\n                android:layout_width=\"wrap_content\"\n                android:layout_height=\"wrap_content\"\n                android:layout_marginStart=\"4dp\"\n                android:textColor=\"?android:attr/textColorSecondary\"\n                android:textSize=\"16sp\"\n                tools:text=\"#1\" />\n\n        </LinearLayout>\n\n        <TextView\n            android:id=\"@+id/tv_prompt\"\n            android:layout_width=\"match_parent\"\n            android:layout_height=\"wrap_content\"\n            android:layout_marginStart=\"2dp\"\n            android:ellipsize=\"end\"\n            android:maxLines=\"2\"\n            android:textColor=\"?android:attr/textColorSecondary\"\n            tools:text=\"this is remark.this is remark.this is remark.this is remark.this is remark.this is remark.this is remark.this is remark.\"\n            tools:visibility=\"visible\" />\n\n    </LinearLayout>\n\n    <com.google.android.material.button.MaterialButton\n        android:id=\"@+id/btn_edit\"\n        style=\"@style/Widget.Material3.Button.IconButton\"\n        android:layout_width=\"wrap_content\"\n        android:layout_height=\"wrap_content\"\n        android:insetTop=\"0dp\"\n        android:insetBottom=\"0dp\"\n        android:minHeight=\"0dp\"\n        app:icon=\"@drawable/baseline_edit_24\" />\n\n    <com.google.android.material.button.MaterialButton\n        android:id=\"@+id/btn_delete\"\n        style=\"@style/Widget.Material3.Button.IconButton\"\n        android:layout_width=\"wrap_content\"\n        android:layout_height=\"wrap_content\"\n        android:insetTop=\"0dp\"\n        android:insetBottom=\"0dp\"\n        android:minHeight=\"0dp\"\n        app:icon=\"@drawable/ic_baseline_delete_24\"\n        app:iconTint=\"@color/red\" />\n\n</LinearLayout>"
  },
  {
    "path": "app/src/main/res/layout/item_h_keyframes.xml",
    "content": "<?xml version=\"1.0\" encoding=\"utf-8\"?>\n\n<com.google.android.material.card.MaterialCardView xmlns:android=\"http://schemas.android.com/apk/res/android\"\n    xmlns:tools=\"http://schemas.android.com/tools\"\n    style=\"@style/Widget.Material3.CardView.Elevated\"\n    android:layout_width=\"match_parent\"\n    android:layout_height=\"wrap_content\"\n    android:layout_marginBottom=\"4dp\">\n\n    <LinearLayout\n        android:layout_width=\"match_parent\"\n        android:layout_height=\"wrap_content\"\n        android:orientation=\"vertical\"\n        android:padding=\"8dp\">\n\n        <LinearLayout\n            android:layout_width=\"match_parent\"\n            android:layout_height=\"wrap_content\"\n            android:gravity=\"top\"\n            android:orientation=\"horizontal\">\n\n            <TextView\n                android:id=\"@+id/tv_title\"\n                android:layout_width=\"0dp\"\n                android:layout_height=\"wrap_content\"\n                android:layout_weight=\"1\"\n                android:ellipsize=\"end\"\n                android:maxLines=\"2\"\n                android:textSize=\"18sp\"\n                tools:text=\"図書室ノ彼女 THE ANIMATION\" />\n\n            <ImageButton\n                android:id=\"@+id/btn_edit\"\n                android:layout_width=\"wrap_content\"\n                android:layout_height=\"wrap_content\"\n                android:layout_marginStart=\"4dp\"\n                android:layout_marginEnd=\"12dp\"\n                android:background=\"@drawable/baseline_more_horiz_24\"\n                android:backgroundTint=\"?android:colorPrimary\"\n                android:contentDescription=\"@null\" />\n\n        </LinearLayout>\n\n        <TextView\n            android:id=\"@+id/tv_video_code\"\n            android:layout_width=\"match_parent\"\n            android:layout_height=\"wrap_content\"\n            android:ellipsize=\"end\"\n            android:maxLines=\"1\"\n            android:paddingVertical=\"4dp\"\n            android:textSize=\"14sp\"\n            tools:text=\"123456\" />\n\n        <View\n            android:layout_width=\"120dp\"\n            android:layout_height=\"2dp\"\n            android:background=\"@color/red\" />\n\n        <androidx.recyclerview.widget.RecyclerView\n            android:id=\"@+id/rv_h_keyframe\"\n            android:layout_width=\"match_parent\"\n            android:layout_height=\"wrap_content\" />\n\n    </LinearLayout>\n</com.google.android.material.card.MaterialCardView>"
  },
  {
    "path": "app/src/main/res/layout/item_h_subscription.xml",
    "content": "<androidx.constraintlayout.widget.ConstraintLayout 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=\"wrap_content\"\n    android:layout_height=\"wrap_content\"\n    android:gravity=\"center\">\n\n    <ImageView\n        android:id=\"@+id/iv_artist\"\n        android:layout_width=\"@dimen/subscribe_avatar_size\"\n        android:layout_height=\"@dimen/subscribe_avatar_size\"\n        android:layout_marginTop=\"12dp\"\n        android:layout_marginBottom=\"4dp\"\n        app:layout_constraintEnd_toEndOf=\"parent\"\n        app:layout_constraintStart_toStartOf=\"parent\"\n        app:layout_constraintTop_toTopOf=\"parent\" />\n\n    <TextView\n        android:id=\"@+id/tv_artist\"\n        android:layout_width=\"@dimen/subscribe_text_width\"\n        android:layout_height=\"wrap_content\"\n        android:ellipsize=\"end\"\n        android:maxLines=\"1\"\n        android:paddingHorizontal=\"2dp\"\n        android:paddingVertical=\"4dp\"\n        android:textAlignment=\"center\"\n        android:textSize=\"12sp\"\n        app:layout_constraintEnd_toEndOf=\"parent\"\n        app:layout_constraintStart_toStartOf=\"parent\"\n        app:layout_constraintTop_toBottomOf=\"@id/iv_artist\"\n        tools:text=\"PROPPPPPPP\" />\n\n    <ImageButton\n        android:id=\"@+id/btn_delete\"\n        android:layout_width=\"24dp\"\n        android:layout_height=\"24dp\"\n        android:background=\"?attr/selectableItemBackgroundBorderless\"\n        android:contentDescription=\"@string/delete\"\n        android:src=\"@drawable/baseline_remove_circle_24\"\n        android:visibility=\"gone\"\n        app:layout_constraintEnd_toEndOf=\"parent\"\n        app:layout_constraintTop_toTopOf=\"parent\" />\n\n    <com.google.android.material.checkbox.MaterialCheckBox\n        android:id=\"@+id/cb_select\"\n        android:layout_width=\"24dp\"\n        android:layout_height=\"24dp\"\n        android:clickable=\"false\"\n        android:focusable=\"false\"\n        android:minWidth=\"0dp\"\n        android:minHeight=\"0dp\"\n        android:visibility=\"gone\"\n        app:layout_constraintStart_toStartOf=\"parent\"\n        app:layout_constraintTop_toTopOf=\"parent\" />\n\n</androidx.constraintlayout.widget.ConstraintLayout>"
  },
  {
    "path": "app/src/main/res/layout/item_hanidock.xml",
    "content": "<?xml version=\"1.0\" encoding=\"utf-8\"?>\n<com.google.android.material.button.MaterialButton xmlns:android=\"http://schemas.android.com/apk/res/android\"\n    xmlns:tools=\"http://schemas.android.com/tools\"\n    android:id=\"@+id/btn_hanidock\"\n    style=\"@style/Widget.Material3.Button.IconButton\"\n    android:layout_width=\"wrap_content\"\n    android:layout_height=\"wrap_content\"\n    android:maxLines=\"2\"\n    tools:icon=\"@drawable/baseline_keyboard_arrow_up_24\" />"
  },
  {
    "path": "app/src/main/res/layout/item_hanime_downloaded.xml",
    "content": "<?xml version=\"1.0\" encoding=\"utf-8\"?>\n<layout 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\n    <data>\n\n    </data>\n\n    <com.google.android.material.card.MaterialCardView\n        style=\"@style/Widget.Material3.CardView.Elevated\"\n        android:layout_width=\"match_parent\"\n        android:layout_height=\"wrap_content\"\n        android:layout_marginBottom=\"8dp\">\n\n        <ImageView\n            android:id=\"@+id/iv_cover_bg\"\n            android:layout_width=\"match_parent\"\n            android:layout_height=\"0dp\"\n            android:contentDescription=\"@null\"\n            android:scaleType=\"centerCrop\" />\n\n        <View\n            android:id=\"@+id/v_cover_bg\"\n            android:layout_width=\"match_parent\"\n            android:layout_height=\"0dp\"\n            android:background=\"@color/per60_transparent_black\" />\n\n        <androidx.constraintlayout.widget.ConstraintLayout\n            android:layout_width=\"match_parent\"\n            android:layout_height=\"wrap_content\"\n            android:clipToPadding=\"false\"\n            android:paddingHorizontal=\"8dp\"\n            android:paddingTop=\"8dp\">\n\n            <com.google.android.material.imageview.ShapeableImageView\n                android:id=\"@+id/iv_cover\"\n                android:layout_width=\"@dimen/video_cover_weight_small\"\n                android:layout_height=\"@dimen/video_cover_height_small\"\n                android:contentDescription=\"@null\"\n                app:layout_constraintBottom_toBottomOf=\"@id/_barrier\"\n                app:layout_constraintStart_toStartOf=\"parent\"\n                app:layout_constraintTop_toTopOf=\"parent\"\n                app:shapeAppearanceOverlay=\"@style/RoundCornerImageView\" />\n\n            <TextView\n                android:id=\"@+id/tv_title\"\n                android:layout_width=\"match_parent\"\n                android:layout_height=\"wrap_content\"\n                android:layout_marginStart=\"8dp\"\n                android:ellipsize=\"end\"\n                android:maxLines=\"2\"\n                android:minLines=\"2\"\n                android:paddingBottom=\"4dp\"\n                android:textAppearance=\"@style/TextAppearance.Material3.TitleMedium\"\n                android:textStyle=\"bold\"\n                app:layout_constrainedHeight=\"true\"\n                app:layout_constrainedWidth=\"true\"\n                app:layout_constraintEnd_toEndOf=\"parent\"\n                app:layout_constraintStart_toEndOf=\"@id/iv_cover\"\n                app:layout_constraintTop_toTopOf=\"parent\"\n                tools:text=\"123333333222222222222233333333333323333333\" />\n\n            <TextView\n                android:id=\"@+id/tv_video_code\"\n                android:layout_width=\"wrap_content\"\n                android:layout_height=\"wrap_content\"\n                android:layout_marginStart=\"8dp\"\n                android:background=\"@drawable/shape_title_mask\"\n                android:paddingHorizontal=\"4dp\"\n                android:textColor=\"@color/black\"\n                android:textSize=\"14sp\"\n                app:layout_constraintBottom_toBottomOf=\"@id/_barrier\"\n                app:layout_constraintStart_toEndOf=\"@id/iv_cover\"\n                app:layout_constraintTop_toBottomOf=\"@id/tv_title\"\n                tools:text=\"123456789\" />\n\n            <View\n                android:id=\"@+id/_divider\"\n                android:layout_width=\"2dp\"\n                android:layout_height=\"10sp\"\n                android:layout_marginStart=\"6dp\"\n                android:background=\"@color/red\"\n                app:layout_constraintBottom_toBottomOf=\"@id/_barrier\"\n                app:layout_constraintEnd_toStartOf=\"@id/tv_size\"\n                app:layout_constraintStart_toEndOf=\"@id/tv_video_code\"\n                app:layout_constraintTop_toBottomOf=\"@id/tv_title\"\n                tools:ignore=\"SmallSp\" />\n\n            <TextView\n                android:id=\"@+id/tv_size\"\n                android:layout_width=\"wrap_content\"\n                android:layout_height=\"wrap_content\"\n                android:layout_marginStart=\"6dp\"\n                android:textSize=\"16sp\"\n                android:textStyle=\"bold\"\n                app:layout_constraintBottom_toBottomOf=\"@id/_barrier\"\n                app:layout_constraintStart_toEndOf=\"@id/_divider\"\n                app:layout_constraintTop_toBottomOf=\"@id/tv_title\"\n                tools:text=\"476.7 MiB\" />\n\n            <TextView\n                android:id=\"@+id/tv_quality\"\n                android:layout_width=\"wrap_content\"\n                android:layout_height=\"wrap_content\"\n                android:layout_marginStart=\"8dp\"\n                android:textSize=\"16sp\"\n                app:layout_constraintBottom_toBottomOf=\"@id/_barrier\"\n                app:layout_constraintEnd_toEndOf=\"parent\"\n                app:layout_constraintTop_toBottomOf=\"@id/tv_title\"\n                tools:text=\"720P\" />\n\n            <TextView\n                android:id=\"@+id/tv_added_time\"\n                android:layout_width=\"wrap_content\"\n                android:layout_height=\"wrap_content\"\n                android:layout_marginStart=\"8dp\"\n                android:layout_marginTop=\"4dp\"\n                android:drawablePadding=\"2dp\"\n                android:gravity=\"center\"\n                android:textSize=\"14sp\"\n                android:textStyle=\"bold\"\n                android:visibility=\"gone\"\n                app:drawableStartCompat=\"@drawable/scaled_added_time\"\n                app:layout_constraintBottom_toBottomOf=\"@id/_barrier\"\n                app:layout_constraintStart_toEndOf=\"@id/iv_cover\"\n                app:layout_constraintTop_toBottomOf=\"@id/tv_quality\"\n                tools:text=\"2021/02/03 15:12\" />\n\n            <TextView\n                android:id=\"@+id/tv_release_date\"\n                android:layout_width=\"wrap_content\"\n                android:layout_height=\"wrap_content\"\n                android:ellipsize=\"end\"\n                android:gravity=\"center\"\n                android:maxLines=\"1\"\n                android:visibility=\"gone\"\n                app:layout_constrainedWidth=\"true\"\n                app:layout_constraintBaseline_toBaselineOf=\"@id/tv_added_time\"\n                app:layout_constraintBottom_toBottomOf=\"@id/_barrier\"\n                app:layout_constraintEnd_toEndOf=\"parent\"\n                tools:text=\"2021-02-02\" />\n\n            <androidx.constraintlayout.widget.Barrier\n                android:id=\"@+id/_barrier\"\n                android:layout_width=\"match_parent\"\n                android:layout_height=\"wrap_content\"\n                app:barrierDirection=\"bottom\"\n                app:constraint_referenced_ids=\"iv_cover, tv_release_date, tv_added_time\" />\n\n            <androidx.constraintlayout.widget.ConstraintLayout\n                android:id=\"@+id/cl_progress\"\n                android:layout_width=\"match_parent\"\n                android:layout_height=\"wrap_content\"\n                android:layout_marginStart=\"-9dp\"\n                android:layout_marginTop=\"8dp\"\n                android:layout_marginEnd=\"-9dp\"\n                android:background=\"@color/per60_transparent_black\"\n                app:layout_constraintEnd_toEndOf=\"parent\"\n                app:layout_constraintStart_toStartOf=\"parent\"\n                app:layout_constraintTop_toBottomOf=\"@id/_barrier\">\n\n                <com.google.android.material.button.MaterialButton\n                    android:id=\"@+id/btn_delete\"\n                    style=\"@style/Widget.Material3.Button.TextButton\"\n                    android:layout_width=\"wrap_content\"\n                    android:layout_height=\"wrap_content\"\n                    android:text=\"@string/delete\"\n                    app:icon=\"@drawable/ic_baseline_delete_24\"\n                    app:iconGravity=\"end\"\n                    app:layout_constraintBottom_toBottomOf=\"parent\"\n                    app:layout_constraintEnd_toStartOf=\"@id/btn_external_playback\"\n                    app:layout_constraintHorizontal_chainStyle=\"spread_inside\"\n                    app:layout_constraintStart_toStartOf=\"parent\"\n                    app:layout_constraintTop_toTopOf=\"parent\" />\n\n                <com.google.android.material.button.MaterialButton\n                    android:id=\"@+id/btn_external_playback\"\n                    style=\"@style/Widget.Material3.Button.TextButton\"\n                    android:layout_width=\"wrap_content\"\n                    android:layout_height=\"wrap_content\"\n                    android:text=\"@string/external_playback\"\n                    app:icon=\"@drawable/open_in_new_24px\"\n                    app:iconGravity=\"start\"\n                    app:iconPadding=\"8dp\"\n                    app:layout_constraintBottom_toBottomOf=\"parent\"\n                    app:layout_constraintEnd_toStartOf=\"@id/btn_local_playback\"\n                    app:layout_constraintHorizontal_chainStyle=\"spread_inside\"\n                    app:layout_constraintStart_toEndOf=\"@id/btn_delete\"\n                    app:layout_constraintTop_toTopOf=\"parent\" />\n\n                <com.google.android.material.button.MaterialButton\n                    android:id=\"@+id/btn_local_playback\"\n                    style=\"@style/Widget.Material3.Button.TextButton\"\n                    android:layout_width=\"wrap_content\"\n                    android:layout_height=\"wrap_content\"\n                    android:text=\"@string/local_playback\"\n                    app:icon=\"@drawable/ic_baseline_play_arrow_24\"\n                    app:iconGravity=\"start\"\n                    app:iconPadding=\"8dp\"\n                    app:layout_constraintBottom_toBottomOf=\"parent\"\n                    app:layout_constraintEnd_toEndOf=\"parent\"\n                    app:layout_constraintHorizontal_chainStyle=\"spread_inside\"\n                    app:layout_constraintStart_toEndOf=\"@id/btn_external_playback\"\n                    app:layout_constraintTop_toTopOf=\"parent\" />\n\n            </androidx.constraintlayout.widget.ConstraintLayout>\n\n        </androidx.constraintlayout.widget.ConstraintLayout>\n\n\n    </com.google.android.material.card.MaterialCardView>\n</layout>"
  },
  {
    "path": "app/src/main/res/layout/item_hanime_downloading.xml",
    "content": "<?xml version=\"1.0\" encoding=\"utf-8\"?>\n<layout 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\n    <data>\n\n    </data>\n\n    <com.google.android.material.card.MaterialCardView\n        style=\"@style/Widget.Material3.CardView.Elevated\"\n        android:layout_width=\"match_parent\"\n        android:layout_height=\"wrap_content\"\n        android:layout_marginBottom=\"8dp\">\n\n        <ImageView\n            android:id=\"@+id/iv_cover_bg\"\n            android:layout_width=\"match_parent\"\n            android:layout_height=\"0dp\"\n            android:contentDescription=\"@null\"\n            android:scaleType=\"centerCrop\" />\n\n        <View\n            android:id=\"@+id/v_cover_bg\"\n            android:layout_width=\"match_parent\"\n            android:layout_height=\"0dp\"\n            android:background=\"@color/per60_transparent_black\" />\n\n        <androidx.constraintlayout.widget.ConstraintLayout\n            android:layout_width=\"match_parent\"\n            android:layout_height=\"wrap_content\"\n            android:clipToPadding=\"false\"\n            android:paddingHorizontal=\"8dp\"\n            android:paddingTop=\"8dp\">\n\n            <com.google.android.material.imageview.ShapeableImageView\n                android:id=\"@+id/iv_cover\"\n                android:layout_width=\"@dimen/video_cover_weight_small\"\n                android:layout_height=\"@dimen/video_cover_height_small\"\n                android:contentDescription=\"@null\"\n                android:scaleType=\"centerCrop\"\n                app:layout_constraintBottom_toBottomOf=\"@id/_barrier\"\n                app:layout_constraintStart_toStartOf=\"parent\"\n                app:layout_constraintTop_toTopOf=\"parent\"\n                app:shapeAppearanceOverlay=\"@style/RoundCornerImageView\" />\n\n            <TextView\n                android:id=\"@+id/tv_title\"\n                android:layout_width=\"match_parent\"\n                android:layout_height=\"wrap_content\"\n                android:layout_marginStart=\"8dp\"\n                android:ellipsize=\"end\"\n                android:maxLines=\"2\"\n                android:minLines=\"2\"\n                android:paddingBottom=\"4dp\"\n                android:textAppearance=\"@style/TextAppearance.Material3.TitleMedium\"\n                app:layout_constrainedHeight=\"true\"\n                app:layout_constrainedWidth=\"true\"\n                app:layout_constraintEnd_toEndOf=\"parent\"\n                app:layout_constraintStart_toEndOf=\"@id/iv_cover\"\n                app:layout_constraintTop_toTopOf=\"parent\"\n                tools:text=\"1233333333333333\" />\n\n            <TextView\n                android:id=\"@+id/tv_downloaded_size\"\n                android:layout_width=\"wrap_content\"\n                android:layout_height=\"wrap_content\"\n                android:layout_marginStart=\"8dp\"\n                android:gravity=\"center\"\n                android:textSize=\"15sp\"\n                app:layout_constraintBottom_toTopOf=\"@id/_barrier\"\n                app:layout_constraintStart_toEndOf=\"@id/iv_cover\"\n                app:layout_constraintTop_toBottomOf=\"@id/tv_title\"\n                tools:text=\"12 MiB\" />\n\n            <View\n                android:id=\"@+id/_divider\"\n                android:layout_width=\"2dp\"\n                android:layout_height=\"10sp\"\n                android:layout_marginStart=\"6dp\"\n                android:background=\"@color/red\"\n                app:layout_constraintBottom_toTopOf=\"@id/_barrier\"\n                app:layout_constraintEnd_toStartOf=\"@id/tv_size\"\n                app:layout_constraintStart_toEndOf=\"@id/tv_downloaded_size\"\n                app:layout_constraintTop_toBottomOf=\"@id/tv_title\"\n                tools:ignore=\"SmallSp\" />\n\n            <TextView\n                android:id=\"@+id/tv_size\"\n                android:layout_width=\"wrap_content\"\n                android:layout_height=\"wrap_content\"\n                android:layout_marginStart=\"6dp\"\n                android:gravity=\"center\"\n                android:textSize=\"16sp\"\n                android:textStyle=\"bold\"\n                app:layout_constraintBottom_toTopOf=\"@id/_barrier\"\n                app:layout_constraintStart_toEndOf=\"@id/_divider\"\n                app:layout_constraintTop_toBottomOf=\"@id/tv_title\"\n                tools:text=\"7 MiB\" />\n\n            <TextView\n                android:id=\"@+id/tv_quality\"\n                android:layout_width=\"wrap_content\"\n                android:layout_height=\"wrap_content\"\n                android:ellipsize=\"end\"\n                android:gravity=\"center\"\n                android:maxLines=\"1\"\n                android:textSize=\"16sp\"\n                app:layout_constrainedWidth=\"true\"\n                app:layout_constraintBottom_toTopOf=\"@id/_barrier\"\n                app:layout_constraintEnd_toEndOf=\"parent\"\n                app:layout_constraintTop_toBottomOf=\"@id/tv_title\"\n                tools:text=\"720P\" />\n\n            <androidx.constraintlayout.widget.Barrier\n                android:id=\"@+id/_barrier\"\n                android:layout_width=\"match_parent\"\n                android:layout_height=\"wrap_content\"\n                app:barrierDirection=\"bottom\"\n                app:constraint_referenced_ids=\"iv_cover, tv_quality, tv_size\" />\n\n            <androidx.constraintlayout.widget.ConstraintLayout\n                android:id=\"@+id/cl_progress\"\n                android:layout_width=\"match_parent\"\n                android:layout_height=\"wrap_content\"\n                android:layout_marginStart=\"-9dp\"\n                android:layout_marginTop=\"8dp\"\n                android:layout_marginEnd=\"-9dp\"\n                android:background=\"@color/per60_transparent_black\"\n                app:layout_constraintEnd_toEndOf=\"parent\"\n                app:layout_constraintStart_toStartOf=\"parent\"\n                app:layout_constraintTop_toBottomOf=\"@id/_barrier\">\n\n                <View\n                    android:id=\"@+id/v_progress\"\n                    android:layout_width=\"0dp\"\n                    android:layout_height=\"0dp\"\n                    android:layout_marginTop=\"1dp\"\n                    android:background=\"@color/per10_transparent_white\"\n                    app:layout_constraintBottom_toBottomOf=\"parent\"\n                    app:layout_constraintStart_toStartOf=\"parent\"\n                    app:layout_constraintTop_toTopOf=\"parent\" />\n\n                <com.google.android.material.button.MaterialButton\n                    android:id=\"@+id/btn_cancel\"\n                    style=\"@style/Widget.Material3.Button.TextButton\"\n                    android:layout_width=\"wrap_content\"\n                    android:layout_height=\"wrap_content\"\n                    android:text=\"@string/cancel_download\"\n                    app:icon=\"@drawable/ic_baseline_close_24\"\n                    app:iconGravity=\"end\"\n                    app:layout_constraintBottom_toBottomOf=\"parent\"\n                    app:layout_constraintEnd_toStartOf=\"@id/btn_start\"\n                    app:layout_constraintHorizontal_chainStyle=\"spread_inside\"\n                    app:layout_constraintStart_toStartOf=\"parent\"\n                    app:layout_constraintTop_toTopOf=\"parent\" />\n\n                <com.google.android.material.button.MaterialButton\n                    android:id=\"@+id/btn_start\"\n                    style=\"@style/Widget.Material3.Button.TextButton\"\n                    android:layout_width=\"wrap_content\"\n                    android:layout_height=\"wrap_content\"\n                    android:text=\"@string/start_download\"\n                    app:icon=\"@drawable/ic_baseline_play_arrow_24\"\n                    app:iconGravity=\"start\"\n                    app:iconPadding=\"8dp\"\n                    app:layout_constraintBottom_toBottomOf=\"parent\"\n                    app:layout_constraintEnd_toEndOf=\"parent\"\n                    app:layout_constraintHorizontal_chainStyle=\"spread_inside\"\n                    app:layout_constraintStart_toEndOf=\"@id/btn_cancel\"\n                    app:layout_constraintTop_toTopOf=\"parent\" />\n\n            </androidx.constraintlayout.widget.ConstraintLayout>\n\n        </androidx.constraintlayout.widget.ConstraintLayout>\n\n\n    </com.google.android.material.card.MaterialCardView>\n</layout>"
  },
  {
    "path": "app/src/main/res/layout/item_hanime_preview_news.xml",
    "content": "<?xml version=\"1.0\" encoding=\"utf-8\"?>\n<layout 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\n    <data>\n\n    </data>\n\n    <com.google.android.material.card.MaterialCardView\n        android:layout_width=\"match_parent\"\n        android:layout_height=\"match_parent\"\n        android:layout_marginBottom=\"64dp\"\n        android:paddingVertical=\"4dp\"\n        app:strokeColor=\"@android:color/transparent\">\n\n        <ImageView\n            android:id=\"@+id/iv_cover_big\"\n            android:layout_width=\"match_parent\"\n            android:layout_height=\"match_parent\"\n            android:contentDescription=\"@null\"\n            android:scaleType=\"centerCrop\" />\n\n        <ImageView\n            android:layout_width=\"match_parent\"\n            android:layout_height=\"match_parent\"\n            android:background=\"@color/per70_transparent_black\"\n            android:contentDescription=\"@null\" />\n\n        <androidx.constraintlayout.widget.ConstraintLayout\n            android:layout_width=\"match_parent\"\n            android:layout_height=\"wrap_content\">\n\n            <com.google.android.material.imageview.ShapeableImageView\n                android:id=\"@+id/iv_cover\"\n                android:layout_width=\"@dimen/video_cover_simplified_width_large\"\n                android:layout_height=\"@dimen/video_cover_simplified_height_large\"\n                android:layout_margin=\"8dp\"\n                android:contentDescription=\"@null\"\n                app:layout_constraintBottom_toTopOf=\"@id/_BARRIER\"\n                app:layout_constraintStart_toStartOf=\"parent\"\n                app:layout_constraintTop_toTopOf=\"parent\"\n                app:shapeAppearanceOverlay=\"@style/RoundCornerImageView\" />\n\n            <TextView\n                android:id=\"@+id/tv_title\"\n                android:layout_width=\"match_parent\"\n                android:layout_height=\"wrap_content\"\n                android:layout_marginStart=\"8dp\"\n                android:layout_marginTop=\"8dp\"\n                android:layout_marginEnd=\"8dp\"\n                android:background=\"@drawable/shape_title_mask\"\n                android:ellipsize=\"end\"\n                android:maxLines=\"2\"\n                android:padding=\"4dp\"\n                android:textAppearance=\"@style/TextAppearance.Material3.TitleLarge\"\n                android:textSize=\"20sp\"\n                app:layout_constrainedWidth=\"true\"\n                app:layout_constraintEnd_toEndOf=\"parent\"\n                app:layout_constraintStart_toEndOf=\"@id/iv_cover\"\n                app:layout_constraintTop_toTopOf=\"parent\"\n                tools:text=\"123122342424242424342234\" />\n\n            <TextView\n                android:id=\"@+id/tv_video_title\"\n                android:layout_width=\"match_parent\"\n                android:layout_height=\"wrap_content\"\n                android:layout_marginStart=\"8dp\"\n                android:layout_marginTop=\"4dp\"\n                android:layout_marginEnd=\"8dp\"\n                android:ellipsize=\"end\"\n                android:maxLines=\"2\"\n                android:textAppearance=\"@style/TextAppearance.Material3.TitleMedium\"\n                android:textStyle=\"bold\"\n                app:layout_constrainedWidth=\"true\"\n                app:layout_constraintEnd_toEndOf=\"parent\"\n                app:layout_constraintStart_toEndOf=\"@id/iv_cover\"\n                app:layout_constraintTop_toBottomOf=\"@id/tv_title\"\n                tools:text=\"12122131313211312313213131231233\" />\n\n            <TextView\n                android:id=\"@+id/tv_brand\"\n                android:layout_width=\"match_parent\"\n                android:layout_height=\"wrap_content\"\n                android:layout_marginStart=\"8dp\"\n                android:layout_marginTop=\"4dp\"\n                android:layout_marginEnd=\"8dp\"\n                android:ellipsize=\"end\"\n                android:maxLines=\"2\"\n                app:layout_constrainedWidth=\"true\"\n                app:layout_constraintEnd_toEndOf=\"parent\"\n                app:layout_constraintStart_toEndOf=\"@id/iv_cover\"\n                app:layout_constraintTop_toBottomOf=\"@id/tv_video_title\"\n                tools:text=\"1212213133131231233\" />\n\n            <TextView\n                android:id=\"@+id/tv_release_date\"\n                android:layout_width=\"match_parent\"\n                android:layout_height=\"wrap_content\"\n                android:layout_marginStart=\"8dp\"\n                android:layout_marginTop=\"4dp\"\n                android:layout_marginEnd=\"8dp\"\n                android:ellipsize=\"end\"\n                android:maxLines=\"2\"\n                app:layout_constrainedWidth=\"true\"\n                app:layout_constraintEnd_toEndOf=\"parent\"\n                app:layout_constraintStart_toEndOf=\"@id/iv_cover\"\n                app:layout_constraintTop_toBottomOf=\"@id/tv_brand\"\n                tools:text=\"1212213133131231233\" />\n\n            <com.ctetin.expandabletextviewlibrary.ExpandableTextView\n                android:id=\"@+id/tv_introduction\"\n                android:layout_width=\"match_parent\"\n                android:layout_height=\"wrap_content\"\n                android:layout_marginStart=\"8dp\"\n                android:layout_marginTop=\"4dp\"\n                android:layout_marginEnd=\"8dp\"\n                app:ep_contract_text=\"@string/collapse\"\n                app:ep_expand_text=\"@string/expand\"\n                app:ep_max_line=\"3\"\n                app:ep_need_contract=\"true\"\n                app:layout_constrainedWidth=\"true\"\n                app:layout_constraintEnd_toEndOf=\"parent\"\n                app:layout_constraintStart_toEndOf=\"@id/iv_cover\"\n                app:layout_constraintTop_toBottomOf=\"@id/tv_release_date\"\n                tools:text=\"思春期的性學習 2  劇情續上一話，女主春日為了拓展自己的知識經常泡圖書館思春期的性學習 2  劇情續上一話，女主春日為了拓展自己的知識經常泡圖書館思春期的性學習 2  劇情續上一話，女主春日為了拓展自己的知識經常泡圖書館\" />\n\n            <androidx.constraintlayout.widget.Barrier\n                android:id=\"@+id/_BARRIER\"\n                android:layout_width=\"wrap_content\"\n                android:layout_height=\"wrap_content\"\n                app:barrierDirection=\"bottom\"\n                app:constraint_referenced_ids=\"iv_cover, tv_introduction\" />\n\n\n            <com.yenaly.han1meviewer.ui.view.CollapsibleTags\n                android:id=\"@+id/tags\"\n                android:layout_width=\"match_parent\"\n                android:layout_height=\"wrap_content\"\n                android:layout_marginStart=\"8dp\"\n                android:layout_marginTop=\"4dp\"\n                android:layout_marginEnd=\"8dp\"\n                app:layout_constraintBottom_toTopOf=\"@id/rv_preview\"\n                app:layout_constraintEnd_toEndOf=\"parent\"\n                app:layout_constraintStart_toStartOf=\"parent\"\n                app:layout_constraintTop_toBottomOf=\"@id/_BARRIER\" />\n\n\n            <com.yenaly.yenaly_libs.base.view.RecyclerViewAtViewPager2\n                android:id=\"@+id/rv_preview\"\n                android:layout_width=\"match_parent\"\n                android:layout_height=\"match_parent\"\n                android:layout_marginTop=\"4dp\"\n                android:layout_marginBottom=\"4dp\"\n                android:clipToPadding=\"false\"\n                android:paddingStart=\"8dp\"\n                android:paddingEnd=\"8dp\"\n                app:layout_constraintBottom_toBottomOf=\"parent\"\n                app:layout_constraintEnd_toEndOf=\"parent\"\n                app:layout_constraintStart_toStartOf=\"parent\"\n                app:layout_constraintTop_toBottomOf=\"@id/tags\" />\n\n        </androidx.constraintlayout.widget.ConstraintLayout>\n\n    </com.google.android.material.card.MaterialCardView>\n\n</layout>"
  },
  {
    "path": "app/src/main/res/layout/item_hanime_preview_news_pic.xml",
    "content": "<?xml version=\"1.0\" encoding=\"utf-8\"?>\n<com.google.android.material.imageview.ShapeableImageView xmlns:android=\"http://schemas.android.com/apk/res/android\"\n    xmlns:app=\"http://schemas.android.com/apk/res-auto\"\n    android:id=\"@+id/iv_preview_news_pic\"\n    android:layout_width=\"@dimen/hanime_preview_news_pic_width\"\n    android:layout_height=\"@dimen/hanime_preview_news_pic_height\"\n    android:layout_margin=\"4dp\"\n    android:contentDescription=\"@null\"\n    android:scaleType=\"centerCrop\"\n    app:shapeAppearanceOverlay=\"@style/RoundCornerImageView\">\n\n</com.google.android.material.imageview.ShapeableImageView>"
  },
  {
    "path": "app/src/main/res/layout/item_hanime_preview_news_v2.xml",
    "content": "<?xml version=\"1.0\" encoding=\"utf-8\"?>\n<layout xmlns:android=\"http://schemas.android.com/apk/res/android\"\n    xmlns:tools=\"http://schemas.android.com/tools\">\n\n    <data>\n\n    </data>\n\n    <FrameLayout\n        android:layout_width=\"match_parent\"\n        android:layout_height=\"match_parent\">\n\n        <ImageView\n            android:id=\"@+id/iv_cover_big\"\n            android:layout_width=\"match_parent\"\n            android:layout_height=\"match_parent\"\n            android:contentDescription=\"@null\"\n            android:scaleType=\"centerCrop\" />\n\n        <View\n            android:layout_width=\"match_parent\"\n            android:layout_height=\"match_parent\"\n            android:background=\"@color/per70_transparent_black\"\n            android:contentDescription=\"@null\" />\n\n        <LinearLayout\n            android:layout_width=\"match_parent\"\n            android:layout_height=\"match_parent\"\n            android:orientation=\"vertical\"\n            android:clipToPadding=\"false\"\n            android:paddingHorizontal=\"8dp\">\n\n            <LinearLayout\n                android:layout_marginStart=\"-8dp\"\n                android:layout_width=\"match_parent\"\n                android:layout_marginTop=\"8dp\"\n                android:layout_marginEnd=\"8dp\"\n                android:layout_height=\"wrap_content\">\n\n                <View\n                    android:layout_width=\"8dp\"\n                    android:layout_height=\"match_parent\"\n                    android:layout_marginVertical=\"4dp\"\n                    android:background=\"@color/red\" />\n\n                <TextView\n                    android:id=\"@+id/tv_title\"\n                    style=\"@style/TextAppearance.Material3.TitleLarge\"\n                    android:layout_width=\"match_parent\"\n                    android:layout_height=\"wrap_content\"\n                    android:layout_marginStart=\"8dp\"\n                    android:ellipsize=\"end\"\n                    android:textSize=\"24sp\"\n                    android:textStyle=\"bold\"\n                    tools:text=\"1234 -v 125\" />\n\n            </LinearLayout>\n\n            <TextView\n                android:id=\"@+id/tv_video_title\"\n                android:layout_width=\"match_parent\"\n                android:layout_height=\"wrap_content\"\n                android:layout_marginStart=\"8dp\"\n                android:layout_marginTop=\"4dp\"\n                android:layout_marginEnd=\"8dp\"\n                android:ellipsize=\"end\"\n                android:maxLines=\"2\"\n                android:textAppearance=\"@style/TextAppearance.Material3.TitleMedium\"\n                android:textStyle=\"bold\"\n                tools:text=\"12122131313211312313213131231233\" />\n\n            <TextView\n                android:id=\"@+id/tv_brand\"\n                android:layout_width=\"match_parent\"\n                android:layout_height=\"wrap_content\"\n                android:layout_marginStart=\"8dp\"\n                android:layout_marginTop=\"4dp\"\n                android:layout_marginEnd=\"8dp\"\n                android:ellipsize=\"end\"\n                android:maxLines=\"2\"\n                tools:text=\"1212213133131231233\" />\n\n            <TextView\n                android:id=\"@+id/tv_release_date\"\n                android:layout_width=\"match_parent\"\n                android:layout_height=\"wrap_content\"\n                android:layout_marginStart=\"8dp\"\n                android:layout_marginTop=\"4dp\"\n                android:layout_marginEnd=\"8dp\"\n                android:ellipsize=\"end\"\n                android:maxLines=\"2\"\n                tools:text=\"1212213133131231233\" />\n\n            <TextView\n                android:id=\"@+id/tv_introduction\"\n                android:layout_width=\"match_parent\"\n                android:layout_height=\"wrap_content\"\n                android:layout_marginStart=\"8dp\"\n                android:layout_marginTop=\"4dp\"\n                android:layout_marginEnd=\"8dp\"\n                tools:text=\"思春期的性學習 2  劇情續上一話，女主春日為了拓展自己的知識經常泡圖書館思春期的性學習 2  劇情續上一話，女主春日為了拓展自己的知識經常泡圖書館思春期的性學習 2  劇情續上一話，女主春日為了拓展自己的知識經常泡圖書館\" />\n\n            <com.yenaly.han1meviewer.ui.view.CollapsibleTags\n                android:id=\"@+id/tags\"\n                android:layout_width=\"match_parent\"\n                android:layout_height=\"wrap_content\"\n                android:layout_marginStart=\"8dp\"\n                android:layout_marginTop=\"4dp\"\n                android:layout_marginEnd=\"8dp\" />\n\n            <com.yenaly.yenaly_libs.base.view.RecyclerViewAtViewPager2\n                android:id=\"@+id/rv_preview\"\n                android:layout_width=\"match_parent\"\n                android:layout_height=\"wrap_content\"\n                android:layout_marginTop=\"4dp\"\n                android:layout_marginBottom=\"4dp\"\n                android:clipToPadding=\"false\"\n                android:paddingStart=\"8dp\"\n                android:paddingEnd=\"8dp\" />\n        </LinearLayout>\n\n    </FrameLayout>\n\n\n</layout>"
  },
  {
    "path": "app/src/main/res/layout/item_hanime_updated.xml",
    "content": "<?xml version=\"1.0\" encoding=\"utf-8\"?>\n<layout 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\n    <data>\n\n    </data>\n\n    <com.google.android.material.card.MaterialCardView\n        style=\"@style/Widget.Material3.CardView.Elevated\"\n        android:layout_width=\"match_parent\"\n        android:layout_height=\"wrap_content\"\n        android:layout_marginBottom=\"8dp\">\n\n        <ImageView\n            android:id=\"@+id/iv_cover_bg\"\n            android:layout_width=\"match_parent\"\n            android:layout_height=\"0dp\"\n            android:contentDescription=\"@null\"\n            android:scaleType=\"centerCrop\" />\n\n        <View\n            android:id=\"@+id/v_cover_bg\"\n            android:layout_width=\"match_parent\"\n            android:layout_height=\"0dp\"\n            android:background=\"@color/per60_transparent_black\" />\n\n        <androidx.constraintlayout.widget.ConstraintLayout\n            android:layout_width=\"match_parent\"\n            android:layout_height=\"wrap_content\"\n            android:clipToPadding=\"false\"\n            android:paddingHorizontal=\"8dp\"\n            android:paddingTop=\"8dp\">\n\n            <com.google.android.material.imageview.ShapeableImageView\n                android:id=\"@+id/iv_cover\"\n                android:layout_width=\"@dimen/video_cover_weight_small\"\n                android:layout_height=\"@dimen/video_cover_height_small\"\n                android:contentDescription=\"@null\"\n                app:layout_constraintBottom_toBottomOf=\"@id/_barrier\"\n                app:layout_constraintStart_toStartOf=\"parent\"\n                app:layout_constraintTop_toTopOf=\"parent\"\n                app:shapeAppearanceOverlay=\"@style/RoundCornerImageView\" />\n\n            <TextView\n                android:id=\"@+id/tv_title\"\n                android:layout_width=\"match_parent\"\n                android:layout_height=\"wrap_content\"\n                android:layout_marginStart=\"8dp\"\n                android:ellipsize=\"end\"\n                android:maxLines=\"2\"\n                android:minLines=\"2\"\n                android:paddingBottom=\"4dp\"\n                android:textAppearance=\"@style/TextAppearance.Material3.TitleMedium\"\n                android:textStyle=\"bold\"\n                app:layout_constrainedHeight=\"true\"\n                app:layout_constrainedWidth=\"true\"\n                app:layout_constraintEnd_toEndOf=\"parent\"\n                app:layout_constraintStart_toEndOf=\"@id/iv_cover\"\n                app:layout_constraintTop_toTopOf=\"parent\"\n                tools:text=\"123333333222222222222233333333333323333333\" />\n\n            <View\n                android:id=\"@+id/_divider\"\n                android:layout_width=\"2dp\"\n                android:layout_height=\"10sp\"\n                android:layout_marginStart=\"6dp\"\n                android:background=\"@color/red\"\n                app:layout_constraintBottom_toBottomOf=\"@id/_barrier\"\n                app:layout_constraintStart_toEndOf=\"@id/iv_cover\"\n                app:layout_constraintTop_toBottomOf=\"@id/tv_title\"\n                tools:ignore=\"SmallSp\" />\n\n            <TextView\n                android:id=\"@+id/tv_size\"\n                android:layout_width=\"wrap_content\"\n                android:layout_height=\"wrap_content\"\n                android:layout_marginStart=\"6dp\"\n                android:textSize=\"16sp\"\n                android:textStyle=\"bold\"\n                app:layout_constraintBottom_toBottomOf=\"@id/_barrier\"\n                app:layout_constraintEnd_toStartOf=\"@id/tv_size\"\n                app:layout_constraintStart_toEndOf=\"@id/_divider\"\n                app:layout_constraintTop_toBottomOf=\"@id/tv_title\"\n                tools:text=\"476.7 MiB\" />\n\n            <TextView\n                android:id=\"@+id/tv_added_time\"\n                android:layout_width=\"wrap_content\"\n                android:layout_height=\"wrap_content\"\n                android:layout_marginStart=\"8dp\"\n                android:layout_marginTop=\"4dp\"\n                android:drawablePadding=\"2dp\"\n                android:gravity=\"center\"\n                android:textSize=\"14sp\"\n                android:textStyle=\"bold\"\n                android:visibility=\"gone\"\n                app:drawableStartCompat=\"@drawable/scaled_added_time\"\n                app:layout_constraintBottom_toBottomOf=\"@id/_barrier\"\n                app:layout_constraintStart_toEndOf=\"@id/iv_cover\"\n                tools:text=\"2021/02/03 15:12\" />\n\n            <TextView\n                android:id=\"@+id/tv_release_date\"\n                android:layout_width=\"wrap_content\"\n                android:layout_height=\"wrap_content\"\n                android:ellipsize=\"end\"\n                android:gravity=\"center\"\n                android:maxLines=\"1\"\n                android:visibility=\"gone\"\n                app:layout_constrainedWidth=\"true\"\n                app:layout_constraintBaseline_toBaselineOf=\"@id/tv_added_time\"\n                app:layout_constraintBottom_toBottomOf=\"@id/_barrier\"\n                app:layout_constraintEnd_toEndOf=\"parent\"\n                tools:text=\"2021-02-02\" />\n\n            <androidx.constraintlayout.widget.Barrier\n                android:id=\"@+id/_barrier\"\n                android:layout_width=\"match_parent\"\n                android:layout_height=\"wrap_content\"\n                app:barrierDirection=\"bottom\"\n                app:constraint_referenced_ids=\"iv_cover, tv_release_date, tv_added_time\" />\n\n            <androidx.constraintlayout.widget.ConstraintLayout\n                android:id=\"@+id/cl_progress\"\n                android:layout_width=\"match_parent\"\n                android:layout_height=\"wrap_content\"\n                android:layout_marginStart=\"-9dp\"\n                android:layout_marginTop=\"8dp\"\n                android:layout_marginEnd=\"-9dp\"\n                android:background=\"@color/per60_transparent_black\"\n                app:layout_constraintEnd_toEndOf=\"parent\"\n                app:layout_constraintStart_toStartOf=\"parent\"\n                app:layout_constraintTop_toBottomOf=\"@id/_barrier\">\n\n                <com.google.android.material.button.MaterialButton\n                    android:id=\"@+id/btn_delete\"\n                    style=\"@style/Widget.Material3.Button.TextButton\"\n                    android:layout_width=\"wrap_content\"\n                    android:layout_height=\"wrap_content\"\n                    android:text=\"@string/delete\"\n                    app:icon=\"@drawable/ic_baseline_delete_24\"\n                    app:iconGravity=\"end\"\n                    app:layout_constraintBottom_toBottomOf=\"parent\"\n                    app:layout_constraintHorizontal_chainStyle=\"spread_inside\"\n                    app:layout_constraintStart_toStartOf=\"parent\"\n                    app:layout_constraintTop_toTopOf=\"parent\" />\n\n                <com.google.android.material.button.MaterialButton\n                    android:id=\"@+id/btn_install\"\n                    style=\"@style/Widget.Material3.Button.TextButton\"\n                    android:layout_width=\"wrap_content\"\n                    android:layout_height=\"wrap_content\"\n                    android:text=\"@string/local_playback\"\n                    app:icon=\"@drawable/ic_baseline_play_arrow_24\"\n                    app:iconGravity=\"start\"\n                    app:iconPadding=\"8dp\"\n                    app:layout_constraintBottom_toBottomOf=\"parent\"\n                    app:layout_constraintEnd_toEndOf=\"parent\"\n                    app:layout_constraintHorizontal_chainStyle=\"spread_inside\"\n                    app:layout_constraintTop_toTopOf=\"parent\" />\n\n            </androidx.constraintlayout.widget.ConstraintLayout>\n\n        </androidx.constraintlayout.widget.ConstraintLayout>\n\n\n    </com.google.android.material.card.MaterialCardView>\n</layout>"
  },
  {
    "path": "app/src/main/res/layout/item_hanime_updating.xml",
    "content": "<?xml version=\"1.0\" encoding=\"utf-8\"?>\n<layout 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\n    <data>\n\n    </data>\n\n    <com.google.android.material.card.MaterialCardView\n        style=\"@style/Widget.Material3.CardView.Elevated\"\n        android:layout_width=\"match_parent\"\n        android:layout_height=\"wrap_content\"\n        android:layout_marginBottom=\"8dp\">\n\n        <ImageView\n            android:id=\"@+id/iv_cover_bg\"\n            android:layout_width=\"match_parent\"\n            android:layout_height=\"0dp\"\n            android:contentDescription=\"@null\"\n            android:scaleType=\"centerCrop\" />\n\n        <View\n            android:id=\"@+id/v_cover_bg\"\n            android:layout_width=\"match_parent\"\n            android:layout_height=\"0dp\"\n            android:background=\"@color/per60_transparent_black\" />\n\n        <androidx.constraintlayout.widget.ConstraintLayout\n            android:layout_width=\"match_parent\"\n            android:layout_height=\"wrap_content\"\n            android:clipToPadding=\"false\"\n            android:paddingHorizontal=\"8dp\"\n            android:paddingTop=\"8dp\">\n\n            <com.google.android.material.imageview.ShapeableImageView\n                android:id=\"@+id/iv_cover\"\n                android:layout_width=\"@dimen/video_cover_weight_small\"\n                android:layout_height=\"@dimen/video_cover_height_small\"\n                android:contentDescription=\"@null\"\n                android:scaleType=\"centerCrop\"\n                app:layout_constraintBottom_toBottomOf=\"@id/_barrier\"\n                app:layout_constraintStart_toStartOf=\"parent\"\n                app:layout_constraintTop_toTopOf=\"parent\"\n                app:shapeAppearanceOverlay=\"@style/RoundCornerImageView\" />\n\n            <TextView\n                android:id=\"@+id/tv_title\"\n                android:layout_width=\"match_parent\"\n                android:layout_height=\"wrap_content\"\n                android:layout_marginStart=\"8dp\"\n                android:ellipsize=\"end\"\n                android:maxLines=\"2\"\n                android:minLines=\"2\"\n                android:paddingBottom=\"4dp\"\n                android:textAppearance=\"@style/TextAppearance.Material3.TitleMedium\"\n                app:layout_constrainedHeight=\"true\"\n                app:layout_constrainedWidth=\"true\"\n                app:layout_constraintEnd_toEndOf=\"parent\"\n                app:layout_constraintStart_toEndOf=\"@id/iv_cover\"\n                app:layout_constraintTop_toTopOf=\"parent\"\n                tools:text=\"1233333333333333\" />\n\n            <TextView\n                android:id=\"@+id/tv_downloaded_size\"\n                android:layout_width=\"wrap_content\"\n                android:layout_height=\"wrap_content\"\n                android:layout_marginStart=\"8dp\"\n                android:gravity=\"center\"\n                android:textSize=\"15sp\"\n                app:layout_constraintBottom_toTopOf=\"@id/_barrier\"\n                app:layout_constraintStart_toEndOf=\"@id/iv_cover\"\n                app:layout_constraintTop_toBottomOf=\"@id/tv_title\"\n                tools:text=\"12 MiB\" />\n\n            <View\n                android:id=\"@+id/_divider\"\n                android:layout_width=\"2dp\"\n                android:layout_height=\"10sp\"\n                android:layout_marginStart=\"6dp\"\n                android:background=\"@color/red\"\n                app:layout_constraintBottom_toTopOf=\"@id/_barrier\"\n                app:layout_constraintEnd_toStartOf=\"@id/tv_size\"\n                app:layout_constraintStart_toEndOf=\"@id/tv_downloaded_size\"\n                app:layout_constraintTop_toBottomOf=\"@id/tv_title\"\n                tools:ignore=\"SmallSp\" />\n\n            <TextView\n                android:id=\"@+id/tv_size\"\n                android:layout_width=\"wrap_content\"\n                android:layout_height=\"wrap_content\"\n                android:layout_marginStart=\"6dp\"\n                android:gravity=\"center\"\n                android:textSize=\"16sp\"\n                android:textStyle=\"bold\"\n                app:layout_constraintBottom_toTopOf=\"@id/_barrier\"\n                app:layout_constraintStart_toEndOf=\"@id/_divider\"\n                app:layout_constraintTop_toBottomOf=\"@id/tv_title\"\n                tools:text=\"7 MiB\" />\n\n            <androidx.constraintlayout.widget.Barrier\n                android:id=\"@+id/_barrier\"\n                android:layout_width=\"match_parent\"\n                android:layout_height=\"wrap_content\"\n                app:barrierDirection=\"bottom\"\n                app:constraint_referenced_ids=\"iv_cover, tv_size\" />\n\n            <androidx.constraintlayout.widget.ConstraintLayout\n                android:id=\"@+id/cl_progress\"\n                android:layout_width=\"match_parent\"\n                android:layout_height=\"wrap_content\"\n                android:layout_marginStart=\"-9dp\"\n                android:layout_marginTop=\"8dp\"\n                android:layout_marginEnd=\"-9dp\"\n                android:background=\"@color/per60_transparent_black\"\n                app:layout_constraintEnd_toEndOf=\"parent\"\n                app:layout_constraintStart_toStartOf=\"parent\"\n                app:layout_constraintTop_toBottomOf=\"@id/_barrier\">\n\n                <View\n                    android:id=\"@+id/v_progress\"\n                    android:layout_width=\"0dp\"\n                    android:layout_height=\"0dp\"\n                    android:layout_marginTop=\"1dp\"\n                    android:background=\"@color/per10_transparent_white\"\n                    app:layout_constraintBottom_toBottomOf=\"parent\"\n                    app:layout_constraintStart_toStartOf=\"parent\"\n                    app:layout_constraintTop_toTopOf=\"parent\" />\n\n                <com.google.android.material.button.MaterialButton\n                    android:id=\"@+id/btn_cancel\"\n                    style=\"@style/Widget.Material3.Button.TextButton\"\n                    android:layout_width=\"wrap_content\"\n                    android:layout_height=\"wrap_content\"\n                    android:text=\"@string/cancel_download\"\n                    app:icon=\"@drawable/ic_baseline_close_24\"\n                    app:iconGravity=\"end\"\n                    app:layout_constraintBottom_toBottomOf=\"parent\"\n                    app:layout_constraintEnd_toStartOf=\"@id/btn_start\"\n                    app:layout_constraintHorizontal_chainStyle=\"spread_inside\"\n                    app:layout_constraintStart_toStartOf=\"parent\"\n                    app:layout_constraintTop_toTopOf=\"parent\" />\n\n                <com.google.android.material.button.MaterialButton\n                    android:id=\"@+id/btn_start\"\n                    style=\"@style/Widget.Material3.Button.TextButton\"\n                    android:layout_width=\"wrap_content\"\n                    android:layout_height=\"wrap_content\"\n                    android:text=\"@string/start_download\"\n                    app:icon=\"@drawable/ic_baseline_play_arrow_24\"\n                    app:iconGravity=\"start\"\n                    app:iconPadding=\"8dp\"\n                    app:layout_constraintBottom_toBottomOf=\"parent\"\n                    app:layout_constraintEnd_toEndOf=\"parent\"\n                    app:layout_constraintHorizontal_chainStyle=\"spread_inside\"\n                    app:layout_constraintStart_toEndOf=\"@id/btn_cancel\"\n                    app:layout_constraintTop_toTopOf=\"parent\" />\n\n            </androidx.constraintlayout.widget.ConstraintLayout>\n\n        </androidx.constraintlayout.widget.ConstraintLayout>\n\n\n    </com.google.android.material.card.MaterialCardView>\n</layout>"
  },
  {
    "path": "app/src/main/res/layout/item_hanime_video.xml",
    "content": "<?xml version=\"1.0\" encoding=\"utf-8\"?>\n<layout 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\n    <data>\n\n    </data>\n\n    <FrameLayout\n        android:id=\"@+id/frame\"\n        android:layout_width=\"match_parent\"\n        android:layout_height=\"wrap_content\"\n        android:foreground=\"?selectableItemBackground\"\n        android:paddingHorizontal=\"4dp\"\n        android:paddingBottom=\"8dp\">\n\n        <LinearLayout\n            android:id=\"@+id/linear\"\n            android:layout_width=\"wrap_content\"\n            android:layout_height=\"wrap_content\"\n            android:layout_gravity=\"center\"\n            android:orientation=\"vertical\"\n            tools:ignore=\"UselessParent\">\n\n            <FrameLayout\n                android:id=\"@+id/cover_wrapper\"\n                android:layout_width=\"@dimen/video_cover_width\"\n                android:layout_height=\"@dimen/video_cover_height\">\n\n                <com.google.android.material.imageview.ShapeableImageView\n                    android:id=\"@+id/cover\"\n                    android:layout_width=\"match_parent\"\n                    android:layout_height=\"match_parent\"\n                    android:contentDescription=\"@null\"\n                    app:shapeAppearanceOverlay=\"@style/RoundCornerImageView\" />\n\n                <TextView\n                    android:id=\"@+id/is_playing\"\n                    android:layout_width=\"match_parent\"\n                    android:layout_height=\"match_parent\"\n                    android:background=\"@color/per50_transparent_black\"\n                    android:contentDescription=\"@null\"\n                    android:gravity=\"center\"\n                    android:text=\"@string/now_playing\" />\n\n                <androidx.constraintlayout.widget.ConstraintLayout\n                    android:layout_width=\"match_parent\"\n                    android:layout_height=\"wrap_content\"\n                    android:layout_gravity=\"bottom\"\n                    android:background=\"@drawable/gradient_black_transparent_90\"\n                    android:paddingStart=\"4dp\"\n                    android:paddingEnd=\"4dp\"\n                    android:paddingBottom=\"4dp\">\n\n                    <ImageView\n                        android:id=\"@+id/icon_views\"\n                        android:layout_width=\"@dimen/view_view_and_time_icon_size\"\n                        android:layout_height=\"@dimen/view_view_and_time_icon_size\"\n                        android:background=\"@drawable/ic_baseline_play_circle_outline_24\"\n                        android:contentDescription=\"@null\"\n                        app:layout_constraintBottom_toBottomOf=\"parent\"\n                        app:layout_constraintStart_toStartOf=\"parent\"\n                        app:layout_constraintTop_toTopOf=\"parent\" />\n\n                    <TextView\n                        android:id=\"@+id/views\"\n                        android:layout_width=\"wrap_content\"\n                        android:layout_height=\"wrap_content\"\n                        android:textColor=\"@color/white\"\n                        android:textSize=\"@dimen/video_view_and_time_and_duration\"\n                        app:layout_constraintBottom_toBottomOf=\"parent\"\n                        app:layout_constraintStart_toEndOf=\"@id/icon_views\"\n                        app:layout_constraintTop_toTopOf=\"parent\"\n                        tools:text=\"1234\" />\n\n                    <ImageView\n                        android:id=\"@+id/icon_time\"\n                        android:layout_width=\"@dimen/view_view_and_time_icon_size\"\n                        android:layout_height=\"@dimen/view_view_and_time_icon_size\"\n                        android:layout_marginStart=\"8dp\"\n                        android:background=\"@drawable/ic_baseline_access_time_24\"\n                        android:contentDescription=\"@null\"\n                        app:layout_constraintBottom_toBottomOf=\"parent\"\n                        app:layout_constraintStart_toEndOf=\"@id/views\"\n                        app:layout_constraintTop_toTopOf=\"parent\" />\n\n                    <TextView\n                        android:id=\"@+id/time\"\n                        android:layout_width=\"wrap_content\"\n                        android:layout_height=\"wrap_content\"\n                        android:textColor=\"@color/white\"\n                        android:textSize=\"@dimen/video_view_and_time_and_duration\"\n                        app:layout_constraintBottom_toBottomOf=\"parent\"\n                        app:layout_constraintStart_toEndOf=\"@id/icon_time\"\n                        app:layout_constraintTop_toTopOf=\"parent\"\n                        tools:text=\"1天前\" />\n\n                    <TextView\n                        android:id=\"@+id/duration\"\n                        android:layout_width=\"wrap_content\"\n                        android:layout_height=\"wrap_content\"\n                        android:textColor=\"@color/white\"\n                        android:textSize=\"@dimen/video_view_and_time_and_duration\"\n                        app:layout_constraintBottom_toBottomOf=\"parent\"\n                        app:layout_constraintEnd_toEndOf=\"parent\"\n                        app:layout_constraintTop_toTopOf=\"parent\"\n                        tools:text=\"12:12\" />\n\n                </androidx.constraintlayout.widget.ConstraintLayout>\n\n            </FrameLayout>\n\n            <TextView\n                android:id=\"@+id/title\"\n                android:layout_width=\"match_parent\"\n                android:layout_height=\"wrap_content\"\n                android:layout_marginTop=\"4dp\"\n                android:ellipsize=\"end\"\n                android:paddingStart=\"4dp\"\n                android:paddingEnd=\"4dp\"\n                android:singleLine=\"true\"\n                tools:text=\"This is a title.This is a title.This is a title.This is a title.This is a title.\" />\n\n            <TextView\n                android:id=\"@+id/genre_and_uploader\"\n                android:layout_width=\"match_parent\"\n                android:layout_height=\"wrap_content\"\n                android:layout_marginTop=\"4dp\"\n                android:ellipsize=\"end\"\n                android:paddingStart=\"4dp\"\n                android:paddingEnd=\"4dp\"\n                android:singleLine=\"true\"\n                android:textSize=\"11sp\"\n                tools:text=\"里番 字幕组\" />\n\n        </LinearLayout>\n\n    </FrameLayout>\n\n</layout>"
  },
  {
    "path": "app/src/main/res/layout/item_hanime_video_simplified.xml",
    "content": "<?xml version=\"1.0\" encoding=\"utf-8\"?>\n<layout 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\n    <data>\n\n    </data>\n\n    <FrameLayout\n        android:id=\"@+id/frame\"\n        android:layout_width=\"wrap_content\"\n        android:layout_height=\"wrap_content\">\n\n        <LinearLayout\n            android:id=\"@+id/linear\"\n            android:layout_width=\"wrap_content\"\n            android:layout_height=\"wrap_content\"\n            android:layout_margin=\"4dp\"\n            android:foreground=\"?selectableItemBackground\"\n            android:gravity=\"center\"\n            android:orientation=\"vertical\"\n            android:paddingBottom=\"8dp\"\n            tools:ignore=\"UseCompoundDrawables, UselessParent\">\n\n            <FrameLayout\n                android:id=\"@+id/cover_wrapper\"\n                android:layout_width=\"@dimen/video_cover_simplified_width\"\n                android:layout_height=\"@dimen/video_cover_simplified_height\">\n\n                <com.google.android.material.imageview.ShapeableImageView\n                    android:id=\"@+id/cover\"\n                    android:layout_width=\"match_parent\"\n                    android:layout_height=\"match_parent\"\n                    android:contentDescription=\"@null\"\n                    app:shapeAppearanceOverlay=\"@style/RoundCornerImageView\" />\n\n            </FrameLayout>\n\n            <TextView\n                android:id=\"@+id/title\"\n                android:layout_width=\"match_parent\"\n                android:layout_height=\"wrap_content\"\n                android:layout_marginTop=\"4dp\"\n                android:ellipsize=\"end\"\n                android:maxLines=\"2\"\n                android:minLines=\"2\"\n                android:paddingStart=\"4dp\"\n                android:paddingEnd=\"4dp\"\n                tools:text=\"123123123abc, abc, abc\" />\n\n        </LinearLayout>\n\n    </FrameLayout>\n\n</layout>"
  },
  {
    "path": "app/src/main/res/layout/item_playlist.xml",
    "content": "<?xml version=\"1.0\" encoding=\"utf-8\"?>\n\n<androidx.cardview.widget.CardView 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:layout_marginBottom=\"4dp\">\n\n    <LinearLayout\n        android:layout_width=\"match_parent\"\n        android:layout_height=\"wrap_content\"\n        android:gravity=\"center\"\n        android:orientation=\"horizontal\"\n        android:padding=\"8dp\">\n\n        <TextView\n            android:id=\"@+id/tv_title\"\n            android:layout_width=\"0dp\"\n            android:layout_height=\"wrap_content\"\n            android:layout_weight=\"1\"\n            android:ellipsize=\"end\"\n            android:maxLines=\"1\"\n            android:textSize=\"18sp\"\n            android:textStyle=\"bold\"\n            tools:text=\"Hello\" />\n\n        <TextView\n            android:id=\"@+id/tv_count\"\n            android:layout_width=\"wrap_content\"\n            android:layout_height=\"wrap_content\"\n            android:ellipsize=\"end\"\n            android:maxLines=\"1\"\n            tools:text=\"123\" />\n\n    </LinearLayout>\n</androidx.cardview.widget.CardView>"
  },
  {
    "path": "app/src/main/res/layout/item_preview_tour_simplified.xml",
    "content": "<?xml version=\"1.0\" encoding=\"utf-8\"?>\n<FrameLayout xmlns:android=\"http://schemas.android.com/apk/res/android\"\n    xmlns:app=\"http://schemas.android.com/apk/res-auto\"\n    android:layout_width=\"wrap_content\"\n    android:layout_height=\"wrap_content\"\n    android:layout_margin=\"4dp\">\n\n    <com.google.android.material.imageview.ShapeableImageView\n        android:id=\"@+id/cover\"\n        android:layout_width=\"@dimen/video_cover_simplified_width_small\"\n        android:layout_height=\"@dimen/video_cover_simplified_height_small\"\n        android:contentDescription=\"@null\"\n        app:shapeAppearanceOverlay=\"@style/RoundCornerImageView\" />\n\n</FrameLayout>"
  },
  {
    "path": "app/src/main/res/layout/item_rv_wrapper.xml",
    "content": "<?xml version=\"1.0\" encoding=\"utf-8\"?>\n<FrameLayout xmlns:android=\"http://schemas.android.com/apk/res/android\"\n    android:id=\"@+id/rv_wrapper\"\n    android:layout_width=\"match_parent\"\n    android:layout_height=\"wrap_content\">\n\n    <androidx.recyclerview.widget.RecyclerView\n        android:id=\"@+id/rv\"\n        android:layout_width=\"match_parent\"\n        android:layout_height=\"wrap_content\"\n        android:nestedScrollingEnabled=\"false\" />\n\n</FrameLayout>"
  },
  {
    "path": "app/src/main/res/layout/item_search_history.xml",
    "content": "<?xml version=\"1.0\" encoding=\"utf-8\"?>\n<LinearLayout 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:id=\"@+id/root\"\n    android:layout_width=\"match_parent\"\n    android:layout_height=\"wrap_content\"\n    android:gravity=\"center\"\n    android:orientation=\"horizontal\">\n\n    <!-- 不设置background的原因是search bar已经有统一color了 -->\n\n    <TextView\n        android:id=\"@+id/tv_text\"\n        android:layout_width=\"0dp\"\n        android:layout_height=\"wrap_content\"\n        android:layout_marginStart=\"8dp\"\n        android:layout_weight=\"1\"\n        android:maxLines=\"2\"\n        android:textColor=\"@color/black\"\n        android:textSize=\"18sp\"\n        tools:text=\"Test Ce Shi\" />\n\n    <com.google.android.material.button.MaterialButton\n        android:id=\"@+id/btn_remove\"\n        style=\"@style/Widget.Material3.Button.IconButton\"\n        android:layout_width=\"wrap_content\"\n        android:layout_height=\"wrap_content\"\n        app:icon=\"@drawable/ic_baseline_close_24\"\n        app:iconTint=\"@color/red\" />\n\n</LinearLayout>"
  },
  {
    "path": "app/src/main/res/layout/item_shared_h_keyframes.xml",
    "content": "<?xml version=\"1.0\" encoding=\"utf-8\"?>\n\n<com.google.android.material.card.MaterialCardView 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    style=\"@style/Widget.Material3.CardView.Elevated\"\n    android:layout_width=\"match_parent\"\n    android:layout_height=\"wrap_content\"\n    android:layout_marginHorizontal=\"8dp\"\n    android:layout_marginBottom=\"4dp\">\n\n\n    <LinearLayout\n        android:layout_width=\"match_parent\"\n        android:layout_height=\"wrap_content\"\n        android:orientation=\"vertical\"\n        android:padding=\"8dp\">\n\n        <LinearLayout\n            android:layout_width=\"match_parent\"\n            android:layout_height=\"wrap_content\"\n            android:gravity=\"top\"\n            android:orientation=\"horizontal\">\n\n            <TextView\n                android:id=\"@+id/tv_title\"\n                android:layout_width=\"0dp\"\n                android:layout_height=\"wrap_content\"\n                android:layout_weight=\"1\"\n                android:ellipsize=\"end\"\n                android:maxLines=\"2\"\n                android:textSize=\"18sp\"\n                tools:text=\"図書室ノ彼女 THE ANIMATION\" />\n\n        </LinearLayout>\n\n        <TextView\n            android:id=\"@+id/tv_video_code\"\n            android:layout_width=\"match_parent\"\n            android:layout_height=\"wrap_content\"\n            android:ellipsize=\"end\"\n            android:maxLines=\"1\"\n            android:paddingVertical=\"4dp\"\n            android:textSize=\"14sp\"\n            tools:text=\"123456\" />\n\n        <View\n            android:layout_width=\"120dp\"\n            android:layout_height=\"2dp\"\n            android:background=\"@color/red\" />\n\n        <androidx.recyclerview.widget.RecyclerView\n            android:id=\"@+id/rv_h_keyframe\"\n            android:layout_width=\"match_parent\"\n            android:layout_height=\"wrap_content\" />\n\n    </LinearLayout>\n\n    <androidx.constraintlayout.widget.ConstraintLayout\n        android:layout_width=\"match_parent\"\n        android:layout_height=\"match_parent\">\n\n        <TextView\n            android:id=\"@+id/tv_author\"\n            android:layout_width=\"0dp\"\n            android:layout_height=\"0dp\"\n            android:layout_gravity=\"bottom|end\"\n            android:layout_margin=\"8dp\"\n            android:gravity=\"end|bottom\"\n            android:maxLines=\"1\"\n            android:textColor=\"@color/per50_transparent_grey\"\n            android:textSize=\"18sp\"\n            app:autoSizeTextType=\"uniform\"\n            app:layout_constraintBottom_toBottomOf=\"parent\"\n            app:layout_constraintEnd_toEndOf=\"parent\"\n            app:layout_constraintHeight_percent=\"1\"\n            app:layout_constraintWidth_percent=\"0.3\"\n            tools:text=\"\\@NekoOuO\" />\n\n    </androidx.constraintlayout.widget.ConstraintLayout>\n</com.google.android.material.card.MaterialCardView>"
  },
  {
    "path": "app/src/main/res/layout/item_tag_chip.xml",
    "content": "<?xml version=\"1.0\" encoding=\"utf-8\"?>\n<com.google.android.material.chip.Chip xmlns:android=\"http://schemas.android.com/apk/res/android\"\n    android:id=\"@+id/chip\"\n    style=\"@style/Widget.Material3.Chip.Filter.Elevated\"\n    android:layout_width=\"wrap_content\"\n    android:layout_height=\"wrap_content\">\n\n</com.google.android.material.chip.Chip>"
  },
  {
    "path": "app/src/main/res/layout/item_tag_chip_group.xml",
    "content": "<?xml version=\"1.0\" encoding=\"utf-8\"?>\n<LinearLayout 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    android:padding=\"8dp\">\n\n    <TextView\n        android:id=\"@+id/sub_title\"\n        android:layout_width=\"wrap_content\"\n        android:layout_height=\"wrap_content\"\n        android:textAppearance=\"@style/TextAppearance.Material3.TitleLarge\"\n        tools:text=\"test\" />\n\n    <com.google.android.material.chip.ChipGroup\n        android:id=\"@+id/chip_group\"\n        android:layout_width=\"match_parent\"\n        android:layout_height=\"wrap_content\"\n        android:animateLayoutChanges=\"true\"\n        app:chipSpacingVertical=\"0dp\" />\n\n</LinearLayout>"
  },
  {
    "path": "app/src/main/res/layout/item_video_column_title.xml",
    "content": "<?xml version=\"1.0\" encoding=\"utf-8\"?>\n<LinearLayout 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=\"wrap_content\"\n    android:gravity=\"center\"\n    android:orientation=\"horizontal\"\n    android:padding=\"8dp\">\n\n    <LinearLayout\n        android:layout_width=\"0dp\"\n        android:layout_height=\"wrap_content\"\n        android:layout_weight=\"1\"\n        android:orientation=\"vertical\">\n\n        <include layout=\"@layout/base_column_title\" />\n\n    </LinearLayout>\n\n    <com.google.android.material.button.MaterialButton\n        android:id=\"@+id/more\"\n        style=\"@style/Widget.Material3.Button.IconButton\"\n        android:layout_width=\"wrap_content\"\n        android:layout_height=\"wrap_content\"\n        android:visibility=\"gone\"\n        app:icon=\"@drawable/baseline_arrow_forward_ios_24\" />\n\n\n</LinearLayout>"
  },
  {
    "path": "app/src/main/res/layout/item_video_comment.xml",
    "content": "<?xml version=\"1.0\" encoding=\"utf-8\"?>\n<layout 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\n    <data>\n\n    </data>\n\n    <com.google.android.material.card.MaterialCardView\n        style=\"@style/Widget.Material3.CardView.Elevated\"\n        android:layout_width=\"match_parent\"\n        android:layout_height=\"wrap_content\"\n        android:layout_marginBottom=\"8dp\">\n\n        <androidx.constraintlayout.widget.ConstraintLayout\n            android:layout_width=\"match_parent\"\n            android:layout_height=\"wrap_content\"\n            android:padding=\"8dp\">\n\n            <ImageView\n                android:id=\"@+id/iv_avatar\"\n                android:layout_width=\"@dimen/avatar_size\"\n                android:layout_height=\"@dimen/avatar_size\"\n                android:contentDescription=\"@null\"\n                app:layout_constraintBottom_toTopOf=\"@id/barrier\"\n                app:layout_constraintStart_toStartOf=\"parent\"\n                app:layout_constraintTop_toTopOf=\"parent\" />\n\n            <TextView\n                android:id=\"@+id/tv_username\"\n                android:layout_width=\"match_parent\"\n                android:layout_height=\"wrap_content\"\n                android:layout_marginStart=\"8dp\"\n                android:ellipsize=\"end\"\n                android:maxLines=\"1\"\n                android:textSize=\"16sp\"\n                android:textStyle=\"bold\"\n                app:layout_constrainedWidth=\"true\"\n                app:layout_constraintEnd_toEndOf=\"parent\"\n                app:layout_constraintStart_toEndOf=\"@id/iv_avatar\"\n                app:layout_constraintTop_toTopOf=\"parent\"\n                tools:text=\"error\" />\n\n            <TextView\n                android:id=\"@+id/tv_date\"\n                android:layout_width=\"match_parent\"\n                android:layout_height=\"wrap_content\"\n                android:layout_marginStart=\"8dp\"\n                android:gravity=\"center\"\n                android:maxLines=\"1\"\n                app:layout_constrainedWidth=\"true\"\n                app:layout_constraintStart_toEndOf=\"@id/iv_avatar\"\n                app:layout_constraintTop_toBottomOf=\"@id/tv_username\"\n                tools:text=\"12121231231\" />\n\n            <androidx.constraintlayout.widget.Barrier\n                android:id=\"@+id/barrier\"\n                android:layout_width=\"match_parent\"\n                android:layout_height=\"wrap_content\"\n                app:barrierDirection=\"bottom\"\n                app:constraint_referenced_ids=\"tv_date,tv_username, iv_avatar\" />\n\n            <TextView\n                android:id=\"@+id/tv_content\"\n                android:layout_width=\"match_parent\"\n                android:layout_height=\"wrap_content\"\n                android:layout_marginStart=\"@dimen/comment_content_margin\"\n                android:layout_marginTop=\"16dp\"\n                android:textIsSelectable=\"true\"\n                app:layout_constraintTop_toBottomOf=\"@id/barrier\"\n                tools:text=\"123\" />\n\n            <LinearLayout\n                android:id=\"@+id/linear\"\n                android:layout_width=\"match_parent\"\n                android:layout_height=\"wrap_content\"\n                android:layout_marginStart=\"@dimen/comment_button_margin\"\n                android:layout_marginTop=\"8dp\"\n                android:orientation=\"horizontal\"\n                app:layout_constraintStart_toStartOf=\"parent\"\n                app:layout_constraintTop_toBottomOf=\"@id/tv_content\">\n\n                <com.google.android.material.button.MaterialButton\n                    android:id=\"@+id/btn_thumb_up\"\n                    style=\"@style/Widget.Material3.Button.TextButton.Icon\"\n                    android:layout_width=\"wrap_content\"\n                    android:layout_height=\"wrap_content\"\n                    android:minWidth=\"0dp\"\n                    app:icon=\"@drawable/ic_baseline_thumb_up_off_alt_24\"\n                    tools:text=\"123\" />\n\n                <com.google.android.material.button.MaterialButton\n                    android:id=\"@+id/btn_thumb_down\"\n                    style=\"@style/Widget.Material3.Button.IconButton\"\n                    android:layout_width=\"wrap_content\"\n                    android:layout_height=\"wrap_content\"\n                    android:minWidth=\"0dp\"\n                    android:minHeight=\"0dp\"\n                    app:icon=\"@drawable/ic_baseline_thumb_down_off_alt_24\" />\n\n                <com.google.android.material.button.MaterialButton\n                    android:id=\"@+id/btn_reply\"\n                    style=\"@style/Widget.Material3.Button.TextButton\"\n                    android:layout_width=\"wrap_content\"\n                    android:layout_height=\"wrap_content\"\n                    android:layout_marginStart=\"8dp\"\n                    android:minWidth=\"0dp\"\n                    android:text=\"@string/reply\"\n                    app:icon=\"@drawable/ic_baseline_reply_24\" />\n\n            </LinearLayout>\n\n            <Button\n                android:id=\"@+id/btn_view_more_replies\"\n                style=\"@style/Widget.Material3.Button.TextButton\"\n                android:layout_width=\"wrap_content\"\n                android:layout_height=\"wrap_content\"\n                android:layout_marginStart=\"@dimen/comment_button_margin\"\n                android:text=\"@string/view_more_replies\"\n                app:layout_constraintStart_toStartOf=\"parent\"\n                app:layout_constraintTop_toBottomOf=\"@id/linear\" />\n\n        </androidx.constraintlayout.widget.ConstraintLayout>\n\n    </com.google.android.material.card.MaterialCardView>\n\n</layout>"
  },
  {
    "path": "app/src/main/res/layout/item_video_introduction.xml",
    "content": "<?xml version=\"1.0\" encoding=\"utf-8\"?>\n<layout 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\n    <data>\n\n    </data>\n\n    <LinearLayout\n        android:layout_width=\"match_parent\"\n        android:layout_height=\"wrap_content\"\n        android:orientation=\"vertical\"\n        android:paddingTop=\"8dp\"\n        android:paddingBottom=\"8dp\">\n\n        <androidx.constraintlayout.widget.ConstraintLayout\n            android:id=\"@+id/vg_artist\"\n            android:layout_width=\"match_parent\"\n            android:layout_height=\"wrap_content\">\n\n            <ImageView\n                android:id=\"@+id/iv_artist\"\n                android:layout_width=\"@dimen/avatar_size\"\n                android:layout_height=\"@dimen/avatar_size\"\n                android:layout_marginStart=\"8dp\"\n                android:contentDescription=\"@null\"\n                app:layout_constraintBottom_toBottomOf=\"parent\"\n                app:layout_constraintStart_toStartOf=\"parent\"\n                app:layout_constraintTop_toTopOf=\"parent\" />\n\n            <TextView\n                android:id=\"@+id/tv_artist\"\n                android:layout_width=\"0dp\"\n                android:layout_height=\"wrap_content\"\n                android:layout_marginHorizontal=\"8dp\"\n                android:ellipsize=\"end\"\n                android:maxLines=\"1\"\n                android:textSize=\"16sp\"\n                android:textStyle=\"bold\"\n                app:layout_constraintBottom_toTopOf=\"@id/tv_genre\"\n                app:layout_constraintEnd_toStartOf=\"@id/btn_subscribe\"\n                app:layout_constraintStart_toEndOf=\"@id/iv_artist\"\n                app:layout_constraintTop_toTopOf=\"parent\"\n                tools:text=\"POROOOOOOOOOOOOOOOOOOOOOOOO\" />\n\n            <TextView\n                android:id=\"@+id/tv_genre\"\n                android:layout_width=\"0dp\"\n                android:layout_height=\"wrap_content\"\n                android:layout_marginHorizontal=\"8dp\"\n                app:layout_constraintBottom_toBottomOf=\"parent\"\n                app:layout_constraintEnd_toStartOf=\"@id/btn_subscribe\"\n                app:layout_constraintStart_toEndOf=\"@id/iv_artist\"\n                app:layout_constraintTop_toBottomOf=\"@id/tv_artist\"\n                tools:text=\"3D\" />\n\n            <com.google.android.material.button.MaterialButton\n                android:id=\"@+id/btn_subscribe\"\n                style=\"@style/Widget.Material3.Button.ElevatedButton\"\n                android:layout_width=\"wrap_content\"\n                android:layout_height=\"36dp\"\n                android:layout_marginEnd=\"8dp\"\n                android:insetTop=\"0dp\"\n                android:insetBottom=\"0dp\"\n                app:layout_constraintBottom_toBottomOf=\"parent\"\n                app:layout_constraintEnd_toEndOf=\"parent\"\n                app:layout_constraintTop_toTopOf=\"parent\"\n                tools:text=\"关注\" />\n\n        </androidx.constraintlayout.widget.ConstraintLayout>\n\n        <TextView\n            android:id=\"@+id/title\"\n            android:layout_width=\"match_parent\"\n            android:layout_height=\"wrap_content\"\n            android:layout_marginTop=\"8dp\"\n            android:paddingHorizontal=\"8dp\"\n            android:textAppearance=\"@style/TextAppearance.Material3.TitleLarge\"\n            tools:text=\"思春期のお勉強 第2話 学ぶより経験がしたいお年頃 [中文字幕]\" />\n\n        <TextView\n            android:id=\"@+id/chinese_title\"\n            android:layout_width=\"match_parent\"\n            android:layout_height=\"wrap_content\"\n            android:layout_marginTop=\"4dp\"\n            android:paddingHorizontal=\"8dp\"\n            android:textSize=\"16sp\"\n            tools:text=\"這是中文標題2這是中文標題2這是中文標題2這是中文標題2這是中文標題2\" />\n\n        <LinearLayout\n            android:layout_width=\"match_parent\"\n            android:layout_height=\"wrap_content\"\n            android:layout_marginTop=\"8dp\"\n            android:orientation=\"horizontal\"\n            android:paddingStart=\"8dp\"\n            android:paddingEnd=\"8dp\">\n\n            <TextView\n                android:id=\"@+id/views\"\n                android:layout_width=\"wrap_content\"\n                android:layout_height=\"wrap_content\"\n                tools:text=\"137.6萬次\" />\n\n            <View\n                android:layout_width=\"1dp\"\n                android:layout_height=\"match_parent\"\n                android:layout_margin=\"4dp\"\n                android:background=\"@color/red\" />\n\n            <TextView\n                android:id=\"@+id/uploadTime\"\n                android:layout_width=\"wrap_content\"\n                android:layout_height=\"wrap_content\"\n                tools:text=\"2021-01-01\" />\n\n        </LinearLayout>\n\n        <com.ctetin.expandabletextviewlibrary.ExpandableTextView\n            android:id=\"@+id/tv_introduction\"\n            android:layout_width=\"match_parent\"\n            android:layout_height=\"wrap_content\"\n            android:layout_marginTop=\"8dp\"\n            android:paddingStart=\"8dp\"\n            android:paddingEnd=\"8dp\"\n            android:textAppearance=\"@style/TextAppearance.Material3.BodyMedium\"\n            app:ep_contract_text=\"@string/collapse\"\n            app:ep_expand_text=\"@string/expand\"\n            app:ep_max_line=\"4\"\n            app:ep_need_contract=\"true\"\n            tools:text=\"思春期的性學習 2  劇情續上一話，女主春日為了拓展自己的知識經常泡圖書館\" />\n\n        <com.yenaly.han1meviewer.ui.view.HorizontalNestedScrollView\n            android:id=\"@+id/nsv_buttons\"\n            android:layout_width=\"match_parent\"\n            android:layout_height=\"wrap_content\"\n            android:layout_marginTop=\"8dp\">\n\n            <LinearLayout\n                android:layout_width=\"wrap_content\"\n                android:layout_height=\"wrap_content\"\n                android:gravity=\"center\"\n                android:orientation=\"horizontal\">\n\n                <Button\n                    android:id=\"@+id/btn_add_to_fav\"\n                    style=\"@style/Widget.Material3.Button.TextButton.Icon\"\n                    android:layout_width=\"wrap_content\"\n                    android:layout_height=\"wrap_content\"\n                    android:drawableTop=\"@drawable/ic_baseline_favorite_border_24\"\n                    android:text=\"@string/add_to_fav\" />\n\n                <Button\n                    android:id=\"@+id/btn_my_list\"\n                    style=\"@style/Widget.Material3.Button.TextButton.Icon\"\n                    android:layout_width=\"wrap_content\"\n                    android:layout_height=\"wrap_content\"\n                    android:drawableTop=\"@drawable/baseline_format_list_bulleted_24\"\n                    android:text=\"@string/add_to_playlist\" />\n\n                <Button\n                    android:id=\"@+id/btn_download\"\n                    style=\"@style/Widget.Material3.Button.TextButton.Icon\"\n                    android:layout_width=\"wrap_content\"\n                    android:layout_height=\"wrap_content\"\n                    android:drawableTop=\"@drawable/ic_baseline_download_24\"\n                    android:text=\"@string/download\" />\n\n                <Button\n                    android:id=\"@+id/btn_share\"\n                    style=\"@style/Widget.Material3.Button.TextButton.Icon\"\n                    android:layout_width=\"wrap_content\"\n                    android:layout_height=\"wrap_content\"\n                    android:drawableTop=\"@drawable/ic_baseline_share_24\"\n                    android:text=\"@string/share\" />\n\n                <Button\n                    android:id=\"@+id/btn_to_webpage\"\n                    style=\"@style/Widget.Material3.Button.TextButton.Icon\"\n                    android:layout_width=\"wrap_content\"\n                    android:layout_height=\"wrap_content\"\n                    android:drawableTop=\"@drawable/ic_baseline_language_24\"\n                    android:text=\"@string/jump_to_webpage\" />\n\n            </LinearLayout>\n\n        </com.yenaly.han1meviewer.ui.view.HorizontalNestedScrollView>\n\n        <com.yenaly.han1meviewer.ui.view.CollapsibleTags\n            android:id=\"@+id/tags\"\n            android:layout_width=\"match_parent\"\n            android:layout_height=\"wrap_content\"\n            android:layout_marginStart=\"8dp\"\n            android:layout_marginTop=\"8dp\"\n            android:layout_marginEnd=\"8dp\" />\n\n    </LinearLayout>\n</layout>"
  },
  {
    "path": "app/src/main/res/layout/item_video_tag_chip.xml",
    "content": "<?xml version=\"1.0\" encoding=\"utf-8\"?>\n<com.google.android.material.chip.Chip xmlns:android=\"http://schemas.android.com/apk/res/android\"\n    android:id=\"@+id/chip_tag\"\n    style=\"@style/Widget.MaterialComponents.Chip.Action\"\n    android:layout_width=\"wrap_content\"\n    android:layout_height=\"wrap_content\">\n\n</com.google.android.material.chip.Chip>"
  },
  {
    "path": "app/src/main/res/layout/item_watch_history.xml",
    "content": "<?xml version=\"1.0\" encoding=\"utf-8\"?>\n<layout 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\n    <data>\n\n    </data>\n\n    <com.google.android.material.card.MaterialCardView\n        style=\"@style/Widget.Material3.CardView.Elevated\"\n        android:layout_width=\"match_parent\"\n        android:layout_height=\"wrap_content\"\n        android:layout_marginBottom=\"8dp\">\n\n        <androidx.constraintlayout.widget.ConstraintLayout\n            android:layout_width=\"match_parent\"\n            android:layout_height=\"wrap_content\"\n            android:padding=\"8dp\">\n\n            <com.google.android.material.imageview.ShapeableImageView\n                android:id=\"@+id/iv_cover\"\n                android:layout_width=\"@dimen/video_cover_weight_small\"\n                android:layout_height=\"@dimen/video_cover_height_small\"\n                android:contentDescription=\"@null\"\n                app:layout_constraintBottom_toBottomOf=\"parent\"\n                app:layout_constraintStart_toStartOf=\"parent\"\n                app:layout_constraintTop_toTopOf=\"parent\"\n                app:shapeAppearanceOverlay=\"@style/RoundCornerImageView\" />\n\n            <TextView\n                android:id=\"@+id/tv_title\"\n                android:layout_width=\"match_parent\"\n                android:layout_height=\"wrap_content\"\n                android:layout_marginStart=\"8dp\"\n                android:ellipsize=\"end\"\n                android:maxLines=\"2\"\n                android:minLines=\"2\"\n                android:textAppearance=\"@style/TextAppearance.Material3.TitleMedium\"\n                android:textStyle=\"bold\"\n                app:layout_constrainedHeight=\"true\"\n                app:layout_constrainedWidth=\"true\"\n                app:layout_constraintEnd_toEndOf=\"parent\"\n                app:layout_constraintStart_toEndOf=\"@id/iv_cover\"\n                app:layout_constraintTop_toTopOf=\"parent\"\n                tools:text=\"1233333333333333\" />\n\n            <TextView\n                android:id=\"@+id/tv_added_time\"\n                android:layout_width=\"wrap_content\"\n                android:layout_height=\"wrap_content\"\n                android:layout_marginStart=\"8dp\"\n                android:layout_marginTop=\"8dp\"\n                android:drawablePadding=\"2dp\"\n                android:gravity=\"center\"\n                android:textSize=\"14sp\"\n                android:textStyle=\"bold\"\n                app:drawableStartCompat=\"@drawable/scaled_added_time\"\n                app:layout_constraintBottom_toBottomOf=\"parent\"\n                app:layout_constraintStart_toEndOf=\"@id/iv_cover\"\n                app:layout_constraintTop_toBottomOf=\"@id/tv_title\"\n                tools:text=\"2021/02/03 15:12\" />\n\n            <TextView\n                android:id=\"@+id/tv_release_date\"\n                android:layout_width=\"wrap_content\"\n                android:layout_height=\"wrap_content\"\n                android:ellipsize=\"end\"\n                android:gravity=\"center\"\n                android:maxLines=\"1\"\n                android:visibility=\"gone\"\n                app:layout_constrainedWidth=\"true\"\n                app:layout_constraintBaseline_toBaselineOf=\"@id/tv_added_time\"\n                app:layout_constraintBottom_toBottomOf=\"parent\"\n                app:layout_constraintEnd_toEndOf=\"parent\"\n                tools:text=\"2021-02-02\" />\n\n        </androidx.constraintlayout.widget.ConstraintLayout>\n\n\n    </com.google.android.material.card.MaterialCardView>\n\n</layout>"
  },
  {
    "path": "app/src/main/res/layout/jz_layout_speed.xml",
    "content": "<?xml version=\"1.0\" encoding=\"utf-8\"?>\n<LinearLayout xmlns:android=\"http://schemas.android.com/apk/res/android\"\n    android:id=\"@+id/video_speed_wrapper_area\"\n    android:layout_width=\"240dp\"\n    android:layout_height=\"match_parent\"\n    android:background=\"#88000000\"\n    android:gravity=\"center\"\n    android:orientation=\"vertical\"\n    android:paddingHorizontal=\"4dp\"\n    android:paddingTop=\"16dp\"\n    android:paddingBottom=\"16dp\">\n\n    <androidx.recyclerview.widget.RecyclerView\n        android:id=\"@+id/rv_video_speed\"\n        android:layout_width=\"match_parent\"\n        android:layout_height=\"wrap_content\" />\n\n</LinearLayout>\n"
  },
  {
    "path": "app/src/main/res/layout/layout_collapsible_tag.xml",
    "content": "<?xml version=\"1.0\" encoding=\"utf-8\"?>\n\n<com.google.android.material.card.MaterialCardView 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:id=\"@+id/tag_card_view\"\n    style=\"@style/Widget.Material3.CardView.Elevated\"\n    android:layout_width=\"match_parent\"\n    android:layout_height=\"wrap_content\"\n    android:animateLayoutChanges=\"true\">\n\n    <LinearLayout\n        android:layout_width=\"match_parent\"\n        android:layout_height=\"wrap_content\"\n        android:orientation=\"vertical\"\n        android:paddingStart=\"8dp\"\n        android:paddingTop=\"8dp\"\n        android:paddingEnd=\"8dp\"\n        android:paddingBottom=\"8dp\">\n\n        <androidx.constraintlayout.widget.ConstraintLayout\n            android:layout_width=\"match_parent\"\n            android:layout_height=\"wrap_content\">\n\n            <TextView\n                android:id=\"@+id/_TAG\"\n                android:layout_width=\"wrap_content\"\n                android:layout_height=\"wrap_content\"\n                android:text=\"TAGs\"\n                android:textAppearance=\"@style/TextAppearance.Material3.TitleLarge\"\n                app:layout_constraintBottom_toBottomOf=\"parent\"\n                app:layout_constraintStart_toStartOf=\"parent\"\n                app:layout_constraintTop_toTopOf=\"parent\"\n                tools:ignore=\"HardcodedText\" />\n\n            <com.google.android.material.button.MaterialButton\n                android:id=\"@+id/toggle_button\"\n                style=\"@style/Widget.Material3.Button.IconButton\"\n                android:layout_width=\"wrap_content\"\n                android:layout_height=\"wrap_content\"\n                android:insetTop=\"0dp\"\n                android:insetBottom=\"0dp\"\n                android:minWidth=\"0dp\"\n                android:minHeight=\"0dp\"\n                app:icon=\"@drawable/baseline_keyboard_arrow_down_24\"\n                app:layout_constraintBottom_toBottomOf=\"parent\"\n                app:layout_constraintEnd_toEndOf=\"parent\"\n                app:layout_constraintTop_toTopOf=\"parent\" />\n\n        </androidx.constraintlayout.widget.ConstraintLayout>\n\n        <com.google.android.material.chip.ChipGroup\n            android:id=\"@+id/tag_group\"\n            android:layout_width=\"match_parent\"\n            android:layout_height=\"wrap_content\"\n            app:chipSpacingVertical=\"0dp\">\n\n        </com.google.android.material.chip.ChipGroup>\n\n    </LinearLayout>\n\n</com.google.android.material.card.MaterialCardView>\n"
  },
  {
    "path": "app/src/main/res/layout/layout_empty_view.xml",
    "content": "<?xml version=\"1.0\" encoding=\"utf-8\"?>\n<androidx.constraintlayout.widget.ConstraintLayout 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\n    <TextView\n        android:id=\"@+id/tv_empty\"\n        android:layout_width=\"wrap_content\"\n        android:layout_height=\"wrap_content\"\n        android:gravity=\"center\"\n        android:text=\"@string/here_is_empty\"\n        android:textSize=\"@dimen/err_tip_text_size\"\n        app:layout_constraintBottom_toBottomOf=\"parent\"\n        app:layout_constraintEnd_toEndOf=\"parent\"\n        app:layout_constraintStart_toStartOf=\"parent\"\n        app:layout_constraintTop_toTopOf=\"parent\" />\n\n</androidx.constraintlayout.widget.ConstraintLayout>"
  },
  {
    "path": "app/src/main/res/layout/layout_hanidock.xml",
    "content": "<?xml version=\"1.0\" encoding=\"utf-8\"?>\n<FrameLayout xmlns:android=\"http://schemas.android.com/apk/res/android\"\n    android:layout_width=\"wrap_content\"\n    android:layout_height=\"wrap_content\">\n\n    <androidx.recyclerview.widget.RecyclerView\n        android:id=\"@+id/rv_func\"\n        android:layout_width=\"wrap_content\"\n        android:layout_height=\"wrap_content\"\n        android:background=\"@drawable/shape_download_func_bar\"\n        android:clipToPadding=\"false\"\n        android:elevation=\"8dp\"\n        android:gravity=\"center\"\n        android:orientation=\"vertical\"\n        android:padding=\"4dp\" />\n\n</FrameLayout>"
  },
  {
    "path": "app/src/main/res/layout/layout_hanime_search_bar.xml",
    "content": "<?xml version=\"1.0\" encoding=\"utf-8\"?>\n<LinearLayout 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:id=\"@+id/search_bar\"\n    android:layout_width=\"match_parent\"\n    android:layout_height=\"match_parent\"\n    android:layout_marginHorizontal=\"8dp\"\n    android:background=\"@drawable/search_bar\"\n    android:elevation=\"16dp\"\n    android:gravity=\"center_horizontal\"\n    android:orientation=\"vertical\">\n\n    <LinearLayout\n        android:layout_width=\"match_parent\"\n        android:layout_height=\"48dp\"\n        android:gravity=\"center\"\n        android:orientation=\"horizontal\">\n\n        <com.google.android.material.button.MaterialButton\n            android:id=\"@+id/btn_back\"\n            style=\"@style/Widget.Material3.Button.IconButton\"\n            android:layout_width=\"wrap_content\"\n            android:layout_height=\"wrap_content\"\n            android:insetLeft=\"0dp\"\n            android:insetRight=\"0dp\"\n            android:minWidth=\"0dp\"\n            android:minHeight=\"0dp\"\n            android:padding=\"12dp\"\n            app:icon=\"@drawable/ic_baseline_arrow_back_24\"\n            app:iconTint=\"@color/black\" />\n\n        <com.google.android.material.textfield.TextInputEditText\n            android:id=\"@+id/et_search\"\n            android:layout_width=\"0dp\"\n            android:layout_height=\"40dp\"\n            android:layout_weight=\"1\"\n            android:background=\"@android:color/transparent\"\n            android:ellipsize=\"end\"\n            android:hint=\"@string/search_placeholder\"\n            android:imeOptions=\"actionSearch\"\n            android:inputType=\"text\"\n            android:singleLine=\"true\"\n            android:textColor=\"@color/black\"\n            android:textColorHint=\"@color/grey_700\" />\n\n        <com.google.android.material.button.MaterialButton\n            android:id=\"@+id/btn_tag\"\n            style=\"@style/Widget.Material3.Button.IconButton\"\n            android:layout_width=\"wrap_content\"\n            android:layout_height=\"wrap_content\"\n            android:minWidth=\"0dp\"\n            android:minHeight=\"0dp\"\n            app:icon=\"@drawable/baseline_advanced_search_24\"\n            app:iconTint=\"@color/search_bar_icon_color\" />\n\n        <com.google.android.material.button.MaterialButton\n            android:id=\"@+id/btn_search\"\n            style=\"@style/Widget.Material3.Button.IconButton\"\n            android:layout_width=\"wrap_content\"\n            android:layout_height=\"wrap_content\"\n            android:backgroundTint=\"@color/search_bar_icon_color\"\n            android:insetLeft=\"0dp\"\n            android:minWidth=\"0dp\"\n            android:minHeight=\"0dp\"\n            app:cornerRadius=\"8dp\"\n            app:icon=\"@drawable/ic_baseline_search_24\"\n            app:iconTint=\"@color/white\" />\n\n    </LinearLayout>\n\n    <androidx.recyclerview.widget.RecyclerView\n        android:id=\"@+id/rv_history\"\n        android:layout_width=\"match_parent\"\n        android:layout_height=\"match_parent\"\n        android:visibility=\"gone\"\n        tools:visibility=\"visible\" />\n\n</LinearLayout>"
  },
  {
    "path": "app/src/main/res/layout/layout_header_h_keyframes.xml",
    "content": "<?xml version=\"1.0\" encoding=\"utf-8\"?>\n<LinearLayout 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=\"vertical\">\n\n    <LinearLayout\n        android:layout_width=\"match_parent\"\n        android:layout_height=\"wrap_content\"\n        android:orientation=\"horizontal\"\n        android:paddingVertical=\"8dp\"\n        tools:ignore=\"UselessParent\">\n\n        <View\n            android:layout_width=\"6dp\"\n            android:layout_height=\"match_parent\"\n            android:layout_marginVertical=\"2dp\"\n            android:background=\"@color/red\" />\n\n        <TextView\n            android:id=\"@+id/tv_title\"\n            android:layout_width=\"wrap_content\"\n            android:layout_height=\"wrap_content\"\n            android:layout_marginStart=\"2dp\"\n            android:textAppearance=\"@style/TextAppearance.Material3.TitleLarge\"\n            android:textStyle=\"bold\"\n            tools:text=\"初時間\" />\n\n    </LinearLayout>\n\n\n</LinearLayout>"
  },
  {
    "path": "app/src/main/res/layout/layout_jzvd_with_speed.xml",
    "content": "<?xml version=\"1.0\" encoding=\"utf-8\"?>\n<RelativeLayout 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=\"match_parent\"\n    android:background=\"@android:color/black\"\n    android:descendantFocusability=\"blocksDescendants\"\n    tools:ignore=\"all\">\n\n    <FrameLayout\n        android:id=\"@+id/surface_container\"\n        android:layout_width=\"match_parent\"\n        android:layout_height=\"match_parent\">\n\n    </FrameLayout>\n\n    <ImageView\n        android:id=\"@+id/poster\"\n        android:layout_width=\"match_parent\"\n        android:layout_height=\"match_parent\"\n        android:layout_alignParentStart=\"true\"\n        android:layout_alignParentLeft=\"true\"\n        android:layout_alignParentBottom=\"true\"\n        android:background=\"#000000\"\n        android:scaleType=\"fitCenter\" />\n\n    <LinearLayout\n        android:id=\"@+id/layout_bottom\"\n        android:layout_width=\"match_parent\"\n        android:layout_height=\"50dp\"\n        android:layout_alignParentBottom=\"true\"\n        android:background=\"@drawable/jz_bottom_bg\"\n        android:gravity=\"center_vertical\"\n        android:orientation=\"horizontal\"\n        android:visibility=\"invisible\"\n        tools:visibility=\"visible\">\n\n        <TextView\n            android:id=\"@+id/current\"\n            android:layout_width=\"wrap_content\"\n            android:layout_height=\"wrap_content\"\n            android:layout_marginLeft=\"14dp\"\n            android:text=\"00:00\"\n            android:textColor=\"#ffffff\" />\n\n        <SeekBar\n            android:id=\"@+id/bottom_seek_progress\"\n            android:layout_width=\"0dp\"\n            android:layout_height=\"wrap_content\"\n            android:layout_gravity=\"center_vertical\"\n            android:layout_weight=\"1.0\"\n            android:background=\"@null\"\n            android:max=\"100\"\n            android:maxHeight=\"1dp\"\n            android:minHeight=\"1dp\"\n            android:paddingLeft=\"12dp\"\n            android:paddingTop=\"8dp\"\n            android:paddingRight=\"12dp\"\n            android:paddingBottom=\"8dp\"\n            android:progressDrawable=\"@drawable/jz_bottom_seek_progress\"\n            android:thumb=\"@drawable/jz_bottom_seek_poster\" />\n\n        <TextView\n            android:id=\"@+id/total\"\n            android:layout_width=\"wrap_content\"\n            android:layout_height=\"wrap_content\"\n            android:text=\"00:00\"\n            android:textColor=\"#ffffff\" />\n\n        <TextView\n            android:id=\"@+id/clarity\"\n            android:layout_width=\"wrap_content\"\n            android:layout_height=\"wrap_content\"\n            android:clickable=\"true\"\n            android:paddingLeft=\"20dp\"\n            android:text=\"clarity\"\n            android:textAlignment=\"center\"\n            android:textColor=\"#ffffff\" />\n\n        <TextView\n            android:id=\"@+id/super_resolution\"\n            android:layout_width=\"wrap_content\"\n            android:layout_height=\"wrap_content\"\n            android:clickable=\"true\"\n            android:paddingLeft=\"20dp\"\n            android:text=\"@string/super_resolution\"\n            android:textAlignment=\"center\"\n            android:textColor=\"#ffffff\" />\n\n        <ImageView\n            android:id=\"@+id/vertical_fullscreen\"\n            android:layout_width=\"52.5dp\"\n            android:layout_height=\"fill_parent\"\n            android:paddingBottom=\"14dp\"\n            android:scaleType=\"centerInside\"\n            android:rotation=\"90\"\n            android:src=\"@drawable/screenshot_frame_2_24px\" />\n\n        <ImageView\n            android:id=\"@+id/fullscreen\"\n            android:layout_width=\"52.5dp\"\n            android:layout_height=\"fill_parent\"\n            android:paddingLeft=\"14dp\"\n            android:paddingRight=\"14dp\"\n            android:scaleType=\"centerInside\"\n            android:src=\"@drawable/jz_enlarge\" />\n\n    </LinearLayout>\n\n    <ProgressBar\n        android:id=\"@+id/bottom_progress\"\n        style=\"?android:attr/progressBarStyleHorizontal\"\n        android:layout_width=\"match_parent\"\n        android:layout_height=\"1.5dp\"\n        android:layout_alignParentBottom=\"true\"\n        android:max=\"100\"\n        android:progressDrawable=\"@drawable/jz_bottom_progress\" />\n\n    <ImageView\n        android:id=\"@+id/back_tiny\"\n        android:layout_width=\"24dp\"\n        android:layout_height=\"24dp\"\n        android:layout_marginLeft=\"6dp\"\n        android:layout_marginTop=\"6dp\"\n        android:background=\"@drawable/jz_click_back_tiny_selector\"\n        android:visibility=\"gone\" />\n\n    <LinearLayout\n        android:id=\"@+id/layout_top\"\n        android:layout_width=\"match_parent\"\n        android:layout_height=\"60dp\"\n        android:layout_alignParentStart=\"true\"\n        android:layout_alignParentLeft=\"true\"\n        android:layout_alignParentTop=\"true\"\n        android:background=\"@drawable/jz_title_bg\"\n        android:gravity=\"center_vertical\"\n        android:orientation=\"horizontal\">\n\n        <ImageView\n            android:id=\"@+id/back\"\n            android:layout_width=\"32dp\"\n            android:layout_height=\"match_parent\"\n            android:paddingStart=\"12dp\"\n            android:scaleType=\"centerInside\"\n            android:src=\"@drawable/jz_click_back_selector\" />\n\n        <ImageView\n            android:id=\"@+id/go_home\"\n            android:layout_width=\"32dp\"\n            android:layout_height=\"match_parent\"\n            android:layout_marginStart=\"12dp\"\n            android:scaleType=\"centerInside\"\n            android:src=\"@drawable/outline_home_24\" />\n\n        <TextView\n            android:id=\"@+id/title\"\n            android:layout_width=\"0dp\"\n            android:layout_height=\"wrap_content\"\n            android:layout_centerVertical=\"true\"\n            android:layout_marginHorizontal=\"12dp\"\n            android:layout_weight=\"1\"\n            android:ellipsize=\"end\"\n            android:maxLines=\"2\"\n            android:textColor=\"#ffffff\"\n            android:textSize=\"18sp\"\n            tools:text=\"[中字候补] 一眼顶针，鉴定为纯纯的初生\" />\n\n        <TextView\n            android:id=\"@+id/tv_keyframe\"\n            android:layout_width=\"42dp\"\n            android:layout_height=\"60dp\"\n            android:layout_centerVertical=\"true\"\n            android:layout_marginRight=\"4dp\"\n            android:layout_toStartOf=\"@+id/tv_speed\"\n            android:gravity=\"center\"\n            android:text=\"🥵\"\n            android:textColor=\"#ffffffff\"\n            android:visibility=\"gone\"\n            tools:visibility=\"visible\" />\n\n        <TextView\n            android:id=\"@+id/tv_speed\"\n            android:layout_width=\"42dp\"\n            android:layout_height=\"60dp\"\n            android:layout_centerVertical=\"true\"\n            android:layout_marginEnd=\"8dp\"\n            android:layout_toStartOf=\"@+id/battery_time_layout\"\n            android:gravity=\"center\"\n            android:text=\"@string/speed\"\n            android:textColor=\"#ffffffff\"\n            android:visibility=\"gone\"\n            tools:visibility=\"visible\" />\n\n        <LinearLayout\n            android:id=\"@+id/battery_time_layout\"\n            android:layout_width=\"wrap_content\"\n            android:layout_height=\"wrap_content\"\n            android:layout_alignParentEnd=\"true\"\n            android:layout_alignParentRight=\"true\"\n            android:layout_centerVertical=\"true\"\n            android:layout_marginEnd=\"14dp\"\n            android:layout_marginRight=\"14dp\"\n            android:gravity=\"center_vertical\"\n            android:orientation=\"vertical\"\n            android:visibility=\"gone\"\n            tools:visibility=\"visible\">\n\n            <ImageView\n                android:id=\"@+id/battery_level\"\n                android:layout_width=\"23dp\"\n                android:layout_height=\"10dp\"\n                android:layout_gravity=\"center_horizontal\"\n                android:background=\"@drawable/jz_battery_level_10\" />\n\n            <TextView\n                android:id=\"@+id/video_current_time\"\n                android:layout_width=\"wrap_content\"\n                android:layout_height=\"wrap_content\"\n                android:layout_gravity=\"center_horizontal\"\n                android:gravity=\"center_vertical\"\n                android:maxLines=\"1\"\n                android:textColor=\"#ffffffff\"\n                android:textSize=\"12.0sp\" />\n        </LinearLayout>\n\n    </LinearLayout>\n\n    <ProgressBar\n        android:id=\"@+id/loading\"\n        android:layout_width=\"@dimen/jz_start_button_w_h_normal\"\n        android:layout_height=\"@dimen/jz_start_button_w_h_normal\"\n        android:layout_centerHorizontal=\"true\"\n        android:layout_centerVertical=\"true\"\n        android:indeterminateDrawable=\"@drawable/jz_loading\"\n        android:visibility=\"invisible\" />\n\n    <LinearLayout\n        android:layout_width=\"wrap_content\"\n        android:layout_height=\"wrap_content\"\n        android:layout_below=\"@+id/layout_top\"\n        android:layout_marginStart=\"8dp\"\n        android:layout_marginTop=\"4dp\"\n        android:orientation=\"vertical\">\n\n        <TextView\n            android:id=\"@+id/tv_timer\"\n            android:layout_width=\"wrap_content\"\n            android:layout_height=\"wrap_content\"\n            android:textSize=\"24sp\"\n            android:visibility=\"invisible\"\n            tools:text=\"#9 尻\\n9\"\n            tools:visibility=\"visible\" />\n\n    </LinearLayout>\n\n    <LinearLayout\n        android:id=\"@+id/start_layout\"\n        android:layout_width=\"wrap_content\"\n        android:layout_height=\"wrap_content\"\n        android:layout_centerHorizontal=\"true\"\n        android:layout_centerVertical=\"true\"\n        android:layout_gravity=\"center_vertical\">\n\n        <ImageView\n            android:id=\"@+id/start\"\n            android:layout_width=\"@dimen/jz_start_button_w_h_normal\"\n            android:layout_height=\"@dimen/jz_start_button_w_h_normal\"\n            android:src=\"@drawable/jz_click_play_selector\" />\n    </LinearLayout>\n\n\n    <TextView\n        android:id=\"@+id/replay_text\"\n        android:layout_width=\"wrap_content\"\n        android:layout_height=\"wrap_content\"\n        android:layout_below=\"@+id/start_layout\"\n        android:layout_centerHorizontal=\"true\"\n        android:layout_marginTop=\"6dp\"\n        android:text=\"@string/replay\"\n        android:textColor=\"#ffffff\"\n        android:textSize=\"12sp\"\n        android:visibility=\"invisible\" />\n\n    <LinearLayout\n        android:id=\"@+id/retry_layout\"\n        android:layout_width=\"wrap_content\"\n        android:layout_height=\"wrap_content\"\n        android:layout_centerHorizontal=\"true\"\n        android:layout_centerVertical=\"true\"\n        android:gravity=\"center_horizontal\"\n        android:orientation=\"vertical\"\n        android:visibility=\"gone\">\n\n        <TextView\n            android:layout_width=\"wrap_content\"\n            android:layout_height=\"wrap_content\"\n            android:text=\"@string/video_loading_failed\"\n            android:textColor=\"@android:color/white\"\n            android:textSize=\"14sp\" />\n\n        <TextView\n            android:id=\"@+id/retry_btn\"\n            android:layout_width=\"wrap_content\"\n            android:layout_height=\"wrap_content\"\n            android:layout_marginTop=\"15dp\"\n            android:background=\"@drawable/jz_retry\"\n            android:paddingLeft=\"9dp\"\n            android:paddingTop=\"4dp\"\n            android:paddingRight=\"9dp\"\n            android:paddingBottom=\"4dp\"\n            android:text=\"@string/click_to_restart\"\n            android:textColor=\"@android:color/white\"\n            android:textSize=\"14sp\" />\n    </LinearLayout>\n</RelativeLayout>"
  },
  {
    "path": "app/src/main/res/layout/layout_playlist_header.xml",
    "content": "<?xml version=\"1.0\" encoding=\"utf-8\"?>\n<LinearLayout 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    android:paddingHorizontal=\"8dp\">\n\n    <LinearLayout\n        android:layout_width=\"match_parent\"\n        android:layout_height=\"wrap_content\"\n        android:gravity=\"center\">\n\n        <TextView\n            android:layout_width=\"wrap_content\"\n            android:layout_height=\"wrap_content\"\n            android:layout_marginEnd=\"8dp\"\n            android:text=\"@string/playlist_title\"\n            android:textStyle=\"bold\" />\n\n        <TextView\n            android:id=\"@+id/tv_title\"\n            style=\"@style/TextAppearance.Material3.TitleLarge\"\n            android:layout_width=\"0dp\"\n            android:layout_height=\"wrap_content\"\n            android:layout_weight=\"1\"\n            android:ellipsize=\"end\"\n            android:maxLines=\"1\"\n            tools:text=\"12341231321312321313213131\" />\n\n        <com.google.android.material.button.MaterialButton\n            android:id=\"@+id/btn_delete\"\n            style=\"@style/Widget.Material3.Button.IconButton\"\n            android:layout_width=\"wrap_content\"\n            android:layout_height=\"wrap_content\"\n            android:insetLeft=\"0dp\"\n            android:insetTop=\"0dp\"\n            android:insetRight=\"0dp\"\n            android:insetBottom=\"0dp\"\n            android:minWidth=\"0dp\"\n            android:minHeight=\"0dp\"\n            app:icon=\"@drawable/ic_baseline_delete_24\"\n            app:iconTint=\"@color/red\" />\n\n    </LinearLayout>\n\n    <LinearLayout\n        android:layout_width=\"match_parent\"\n        android:layout_height=\"wrap_content\"\n        android:gravity=\"center\">\n\n        <TextView\n            android:layout_width=\"wrap_content\"\n            android:layout_height=\"wrap_content\"\n            android:layout_marginEnd=\"8dp\"\n            android:text=\"@string/playlist_description\"\n            android:textStyle=\"bold\" />\n\n        <TextView\n            android:id=\"@+id/tv_desc\"\n            android:layout_width=\"0dp\"\n            android:layout_height=\"wrap_content\"\n            android:layout_weight=\"1\"\n            android:ellipsize=\"end\"\n            android:maxLines=\"2\"\n            android:textSize=\"16sp\"\n            tools:text=\"13414we44444444444444444444444444444444444444444\" />\n\n        <com.google.android.material.button.MaterialButton\n            android:id=\"@+id/btn_desc\"\n            style=\"@style/Widget.Material3.Button.IconButton\"\n            android:layout_width=\"wrap_content\"\n            android:layout_height=\"wrap_content\"\n            android:insetLeft=\"0dp\"\n            android:insetTop=\"0dp\"\n            android:insetRight=\"0dp\"\n            android:insetBottom=\"0dp\"\n            android:minWidth=\"0dp\"\n            android:minHeight=\"0dp\"\n            app:icon=\"@drawable/baseline_edit_24\" />\n\n    </LinearLayout>\n\n</LinearLayout>"
  },
  {
    "path": "app/src/main/res/layout/layout_playlist_header_v2.xml",
    "content": "<?xml version=\"1.0\" encoding=\"utf-8\"?>\n<LinearLayout 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:id=\"@+id/banner\"\n    android:layout_width=\"match_parent\"\n    android:layout_height=\"wrap_content\"\n    android:gravity=\"bottom\"\n    android:orientation=\"horizontal\">\n\n    <LinearLayout\n        android:layout_width=\"0dp\"\n        android:layout_height=\"wrap_content\"\n        android:layout_weight=\"1\"\n        android:orientation=\"vertical\">\n\n        <TextView\n            android:id=\"@+id/tv_title\"\n            style=\"@style/TextAppearance.Material3.TitleLarge\"\n            android:layout_width=\"wrap_content\"\n            android:layout_height=\"wrap_content\"\n            android:layout_marginStart=\"8dp\"\n            android:ellipsize=\"end\"\n            android:maxLines=\"1\"\n            android:padding=\"4dp\"\n            android:textAlignment=\"center\"\n            android:textColor=\"@color/white\"\n            android:textStyle=\"bold\"\n            tools:text=\"1234 -v 125\" />\n\n        <TextView\n            android:id=\"@+id/tv_desc\"\n            android:layout_width=\"wrap_content\"\n            android:layout_height=\"wrap_content\"\n            android:layout_marginStart=\"8dp\"\n            android:layout_marginTop=\"2dp\"\n            android:ellipsize=\"end\"\n            android:maxLines=\"2\"\n            android:padding=\"4dp\"\n            tools:text=\"131231313566666666666624444444444444444466666\" />\n\n    </LinearLayout>\n\n    <com.google.android.material.button.MaterialButton\n        android:id=\"@+id/btn_delete\"\n        style=\"@style/Widget.Material3.Button.IconButton\"\n        android:layout_width=\"wrap_content\"\n        android:layout_height=\"wrap_content\"\n        android:layout_marginStart=\"8dp\"\n        android:backgroundTint=\"@color/red\"\n        app:icon=\"@drawable/ic_baseline_delete_24\"\n        app:iconTint=\"@color/white\" />\n\n    <com.google.android.material.button.MaterialButton\n        android:id=\"@+id/btn_edit\"\n        style=\"@style/Widget.Material3.Button.IconButton\"\n        android:layout_width=\"wrap_content\"\n        android:layout_height=\"wrap_content\"\n        android:layout_marginEnd=\"8dp\"\n        android:backgroundTint=\"@color/per70_transparent_white\"\n        app:icon=\"@drawable/baseline_edit_24\"\n        app:iconTint=\"@color/red\" />\n\n</LinearLayout>"
  },
  {
    "path": "app/src/main/res/layout/layout_rv_scrollbars.xml",
    "content": "<?xml version=\"1.0\" encoding=\"utf-8\"?>\n<androidx.recyclerview.widget.RecyclerView xmlns:android=\"http://schemas.android.com/apk/res/android\"\n    android:layout_width=\"match_parent\"\n    android:scrollbars=\"vertical\"\n    android:layout_height=\"match_parent\">\n\n</androidx.recyclerview.widget.RecyclerView>"
  },
  {
    "path": "app/src/main/res/layout/nav_header_ability.xml",
    "content": "<?xml version=\"1.0\" encoding=\"utf-8\"?>\n<LinearLayout xmlns:android=\"http://schemas.android.com/apk/res/android\"\n    xmlns:tools=\"http://schemas.android.com/tools\"\n    xmlns:app=\"http://schemas.android.com/apk/res-auto\"\n    android:layout_width=\"match_parent\"\n    android:layout_height=\"wrap_content\"\n    android:orientation=\"vertical\"\n    android:gravity=\"center_horizontal\"\n    android:paddingBottom=\"36dp\"\n    tools:ignore=\"UseCompoundDrawables\">\n\n\n    <LinearLayout\n        android:layout_width=\"match_parent\"\n        android:layout_height=\"match_parent\"\n        android:orientation=\"horizontal\"\n        android:gravity=\"center_horizontal\"\n        android:paddingBottom=\"16dp\">\n        <ImageView\n            android:id=\"@+id/collapse\"\n            android:layout_width=\"24dp\"\n            android:layout_height=\"24dp\"\n            android:contentDescription=\"@string/collapse\"\n            app:srcCompat=\"@drawable/menu_24px\" />\n    </LinearLayout>\n\n    <ImageView\n        android:id=\"@+id/header_avatar\"\n        android:layout_width=\"@dimen/main_expanded_avatar_size\"\n        android:layout_height=\"@dimen/main_expanded_avatar_size\"\n        android:contentDescription=\"@null\" />\n\n    <TextView\n        android:id=\"@+id/header_username\"\n        android:layout_width=\"wrap_content\"\n        android:layout_height=\"wrap_content\"\n        android:textSize=\"16sp\" />\n\n</LinearLayout>"
  },
  {
    "path": "app/src/main/res/layout/pop_up_ext_h_time_picker.xml",
    "content": "<?xml version=\"1.0\" encoding=\"utf-8\"?>\n<LinearLayout 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=\"wrap_content\"\n    android:orientation=\"vertical\">\n\n    <androidx.constraintlayout.widget.ConstraintLayout\n        android:layout_width=\"match_parent\"\n        android:layout_height=\"wrap_content\"\n        android:paddingTop=\"5dp\"\n        android:paddingBottom=\"5dp\">\n\n        <TextView\n            android:id=\"@+id/btnCancel\"\n            android:layout_width=\"wrap_content\"\n            android:layout_height=\"wrap_content\"\n            android:background=\"?selectableItemBackground\"\n            android:gravity=\"center\"\n            android:paddingStart=\"18dp\"\n            android:paddingTop=\"10dp\"\n            android:paddingEnd=\"18dp\"\n            android:paddingBottom=\"10dp\"\n            android:text=\"@string/xpopup_cancel\"\n            android:textAllCaps=\"false\"\n            android:textColor=\"@color/_xpopup_dark_color\"\n            android:textSize=\"16sp\"\n            app:layout_constraintBottom_toBottomOf=\"parent\"\n            app:layout_constraintStart_toStartOf=\"parent\"\n            app:layout_constraintTop_toTopOf=\"parent\" />\n\n        <com.google.android.material.button.MaterialButton\n            android:id=\"@+id/btnSwitch\"\n            style=\"@style/Widget.Material3.Button.TextButton\"\n            android:layout_width=\"wrap_content\"\n            android:layout_height=\"wrap_content\"\n            app:layout_constraintBottom_toBottomOf=\"parent\"\n            app:layout_constraintEnd_toStartOf=\"@id/btnConfirm\"\n            app:layout_constraintStart_toEndOf=\"@id/btnCancel\"\n            app:layout_constraintTop_toTopOf=\"parent\" />\n\n        <TextView\n            android:id=\"@+id/btnConfirm\"\n            android:layout_width=\"wrap_content\"\n            android:layout_height=\"wrap_content\"\n            android:background=\"?selectableItemBackground\"\n            android:gravity=\"center\"\n            android:paddingStart=\"18dp\"\n            android:paddingTop=\"10dp\"\n            android:paddingEnd=\"18dp\"\n            android:paddingBottom=\"10dp\"\n            android:text=\"@string/xpopup_ok\"\n            android:textAllCaps=\"false\"\n            android:textColor=\"@color/_xpopup_dark_color\"\n            android:textSize=\"16sp\"\n            android:textStyle=\"bold\"\n            app:layout_constraintBottom_toBottomOf=\"parent\"\n            app:layout_constraintEnd_toEndOf=\"parent\"\n            app:layout_constraintTop_toTopOf=\"parent\" />\n\n    </androidx.constraintlayout.widget.ConstraintLayout>\n\n    <LinearLayout\n        android:id=\"@+id/timepicker\"\n        android:layout_width=\"match_parent\"\n        android:layout_height=\"wrap_content\"\n        android:gravity=\"center\"\n        android:minHeight=\"150dp\"\n        android:orientation=\"horizontal\">\n\n        <com.contrarywind.view.WheelView\n            android:id=\"@+id/year\"\n            android:layout_width=\"0dp\"\n            android:layout_height=\"match_parent\"\n            android:layout_weight=\"1\" />\n\n        <com.contrarywind.view.WheelView\n            android:id=\"@+id/month\"\n            android:layout_width=\"0dp\"\n            android:layout_height=\"match_parent\"\n            android:layout_weight=\"1\" />\n\n        <com.contrarywind.view.WheelView\n            android:id=\"@+id/day\"\n            android:layout_width=\"0dp\"\n            android:layout_height=\"match_parent\"\n            android:layout_weight=\"1\" />\n\n        <com.contrarywind.view.WheelView\n            android:id=\"@+id/hour\"\n            android:layout_width=\"0dp\"\n            android:layout_height=\"match_parent\"\n            android:layout_weight=\"1\" />\n\n        <com.contrarywind.view.WheelView\n            android:id=\"@+id/min\"\n            android:layout_width=\"0dp\"\n            android:layout_height=\"match_parent\"\n            android:layout_weight=\"1\" />\n\n        <com.contrarywind.view.WheelView\n            android:id=\"@+id/second\"\n            android:layout_width=\"0dp\"\n            android:layout_height=\"match_parent\"\n            android:layout_weight=\"1\" />\n    </LinearLayout>\n\n\n</LinearLayout>"
  },
  {
    "path": "app/src/main/res/layout/pop_up_fragment_child_comment.xml",
    "content": "<?xml version=\"1.0\" encoding=\"utf-8\"?>\n<layout xmlns:android=\"http://schemas.android.com/apk/res/android\">\n\n    <data>\n\n    </data>\n\n    <LinearLayout\n        android:layout_width=\"match_parent\"\n        android:layout_height=\"match_parent\"\n        android:background=\"@drawable/shape_reply_show_bottom_dialog\"\n        android:orientation=\"vertical\"\n        android:padding=\"8dp\">\n\n        <View\n            android:layout_width=\"100dp\"\n            android:layout_height=\"3dp\"\n            android:layout_gravity=\"center\"\n            android:layout_marginTop=\"8dp\"\n            android:layout_marginBottom=\"16dp\"\n            android:background=\"#80FFFFFF\" />\n\n        <TextView\n            android:id=\"@+id/tv_child_comment\"\n            android:layout_width=\"wrap_content\"\n            android:layout_height=\"wrap_content\"\n            android:layout_marginBottom=\"8dp\"\n            android:text=\"@string/child_comment\"\n            android:textSize=\"24sp\" />\n\n        <androidx.recyclerview.widget.RecyclerView\n            android:id=\"@+id/rv_reply\"\n            android:layout_width=\"match_parent\"\n            android:layout_height=\"match_parent\"\n            android:clipToPadding=\"false\" />\n\n    </LinearLayout>\n</layout>"
  },
  {
    "path": "app/src/main/res/layout/pop_up_fragment_search_options.xml",
    "content": "<?xml version=\"1.0\" encoding=\"utf-8\"?>\n\n\n<layout 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\n    <data>\n\n    </data>\n    <com.google.android.material.card.MaterialCardView\n        android:layout_width=\"match_parent\"\n        android:layout_height=\"wrap_content\"\n        android:clipToPadding=\"false\" >\n        <LinearLayout\n            android:layout_width=\"match_parent\"\n            android:layout_height=\"wrap_content\"\n            android:clipToPadding=\"false\"\n            android:orientation=\"vertical\"\n            android:paddingHorizontal=\"16dp\"\n            android:paddingBottom=\"16dp\">\n\n            <View\n                android:layout_width=\"100dp\"\n                android:layout_height=\"3dp\"\n                android:layout_gravity=\"center\"\n                android:layout_marginTop=\"8dp\"\n                android:layout_marginBottom=\"16dp\"\n                android:background=\"#80FFFFFF\" />\n\n            <androidx.constraintlayout.widget.ConstraintLayout\n                android:layout_width=\"match_parent\"\n                android:layout_height=\"match_parent\">\n\n                <TextView\n                    android:id=\"@+id/textView\"\n                    android:layout_width=\"wrap_content\"\n                    android:layout_height=\"wrap_content\"\n                    android:text=\"@string/advanced_search\"\n                    android:textSize=\"24sp\"\n                    android:textStyle=\"bold\"\n                    app:layout_constraintTop_toTopOf=\"parent\"\n                    app:layout_constraintBottom_toBottomOf=\"parent\"\n                    app:layout_constraintHorizontal_chainStyle=\"spread_inside\"\n                    app:layout_constraintLeft_toLeftOf=\"parent\" />\n\n                <Button\n                    android:id=\"@+id/search\"\n                    android:layout_width=\"wrap_content\"\n                    android:layout_height=\"wrap_content\"\n                    android:text=\"@string/search\"\n                    style=\"@style/Widget.Material3.Button.OutlinedButton\"\n                    app:layout_constraintTop_toTopOf=\"parent\"\n                    app:layout_constraintBottom_toBottomOf=\"parent\"\n                    app:layout_constraintRight_toRightOf=\"parent\" />\n\n            </androidx.constraintlayout.widget.ConstraintLayout>\n\n            <LinearLayout\n                android:id=\"@+id/ll_subscription\"\n                android:layout_width=\"match_parent\"\n                android:layout_height=\"wrap_content\"\n                android:orientation=\"vertical\"\n                android:visibility=\"gone\"\n                tools:visibility=\"visible\">\n\n                <TextView\n                    android:id=\"@+id/tv_subscription\"\n                    android:layout_width=\"wrap_content\"\n                    android:layout_height=\"wrap_content\"\n                    android:layout_marginBottom=\"8dp\"\n                    android:text=\"@string/subscription\"\n                    android:textSize=\"18sp\" />\n\n                <androidx.recyclerview.widget.RecyclerView\n                    android:id=\"@+id/rv_subscription\"\n                    android:layout_width=\"match_parent\"\n                    android:layout_height=\"wrap_content\" />\n\n            </LinearLayout>\n\n            <GridLayout\n                android:layout_width=\"match_parent\"\n                android:layout_height=\"wrap_content\"\n                android:columnCount=\"2\"\n                android:rowCount=\"3\"\n                tools:ignore=\"UselessParent\">\n\n                <com.yenaly.han1meviewer.ui.view.HOptionChip\n                    android:id=\"@+id/type\"\n                    android:layout_height=\"wrap_content\"\n                    android:layout_columnWeight=\"1\"\n                    android:layout_marginTop=\"16dp\"\n                    android:layout_marginEnd=\"8dp\"\n                    android:text=\"@string/type\"\n                    android:textAlignment=\"center\" />\n\n\n                <com.yenaly.han1meviewer.ui.view.HOptionChip\n                    android:id=\"@+id/sort_option\"\n                    android:layout_height=\"wrap_content\"\n                    android:layout_columnWeight=\"1\"\n                    android:layout_marginStart=\"8dp\"\n                    android:layout_marginTop=\"16dp\"\n                    android:text=\"@string/sort_option\"\n                    android:textAlignment=\"center\" />\n\n                <com.yenaly.han1meviewer.ui.view.HOptionChip\n                    android:id=\"@+id/tag\"\n                    android:layout_height=\"wrap_content\"\n                    android:layout_columnWeight=\"1\"\n                    android:layout_marginTop=\"16dp\"\n                    android:layout_marginEnd=\"8dp\"\n                    android:text=\"@string/tag\"\n                    android:textAlignment=\"center\" />\n\n                <com.yenaly.han1meviewer.ui.view.HOptionChip\n                    android:id=\"@+id/brand\"\n                    android:layout_height=\"wrap_content\"\n                    android:layout_columnWeight=\"1\"\n                    android:layout_marginStart=\"8dp\"\n                    android:layout_marginTop=\"16dp\"\n                    android:text=\"@string/brand\"\n                    android:textAlignment=\"center\" />\n\n                <com.yenaly.han1meviewer.ui.view.HOptionChip\n                    android:id=\"@+id/release_date\"\n                    android:layout_height=\"wrap_content\"\n                    android:layout_columnWeight=\"1\"\n                    android:layout_marginTop=\"16dp\"\n                    android:layout_marginEnd=\"8dp\"\n                    android:text=\"@string/release_date\"\n                    android:textAlignment=\"center\" />\n\n                <com.yenaly.han1meviewer.ui.view.HOptionChip\n                    android:id=\"@+id/duration\"\n                    android:layout_height=\"wrap_content\"\n                    android:layout_columnWeight=\"1\"\n                    android:layout_marginStart=\"8dp\"\n                    android:layout_marginTop=\"16dp\"\n                    android:text=\"@string/duration\"\n                    android:textAlignment=\"center\" />\n\n            </GridLayout>\n\n            <TextView\n                android:layout_width=\"wrap_content\"\n                android:layout_height=\"wrap_content\"\n                android:layout_marginTop=\"16dp\"\n                android:text=\"@string/search_options_tips\" />\n\n        </LinearLayout>\n    </com.google.android.material.card.MaterialCardView>\n\n</layout>\n"
  },
  {
    "path": "app/src/main/res/layout/pop_up_hanime_search_tag.xml",
    "content": "<?xml version=\"1.0\" encoding=\"utf-8\"?>\n<LinearLayout 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=\"wrap_content\"\n    android:orientation=\"vertical\">\n\n    <LinearLayout\n        android:id=\"@+id/pair_widely_layout\"\n        android:layout_width=\"match_parent\"\n        android:layout_height=\"wrap_content\"\n        android:orientation=\"vertical\"\n        android:paddingHorizontal=\"16dp\"\n        android:paddingBottom=\"8dp\">\n\n        <com.google.android.material.switchmaterial.SwitchMaterial\n            android:id=\"@+id/pair_widely\"\n            android:layout_width=\"wrap_content\"\n            android:layout_height=\"wrap_content\"\n            android:paddingStart=\"8dp\"\n            android:paddingEnd=\"8dp\"\n            android:text=\"@string/pair_widely\"\n            android:textSize=\"18sp\"\n            app:switchPadding=\"8dp\" />\n\n        <TextView\n            android:layout_width=\"wrap_content\"\n            android:layout_height=\"wrap_content\"\n            android:layout_marginBottom=\"8dp\"\n            android:paddingStart=\"8dp\"\n            android:paddingEnd=\"8dp\"\n            android:text=\"@string/pair_widely_alert\" />\n\n    </LinearLayout>\n\n    <com.google.android.material.tabs.TabLayout\n        android:id=\"@+id/tl_tag\"\n        android:layout_width=\"match_parent\"\n        android:layout_height=\"wrap_content\"\n        android:background=\"?attr/colorSurfaceContainer\"\n        app:tabMode=\"auto\" />\n\n    <androidx.viewpager2.widget.ViewPager2\n        android:id=\"@+id/vp_tag\"\n        android:layout_width=\"match_parent\"\n        android:layout_height=\"300dp\"\n        android:background=\"?attr/colorSurfaceContainer\"\n        android:clipToPadding=\"true\"\n        android:paddingHorizontal=\"8dp\"\n        android:paddingBottom=\"8dp\" />\n\n    <FrameLayout\n        android:id=\"@+id/rv_container\"\n        android:layout_width=\"match_parent\"\n        android:layout_height=\"500dp\"\n        android:paddingHorizontal=\"8dp\"\n        android:visibility=\"gone\" />\n\n</LinearLayout>"
  },
  {
    "path": "app/src/main/res/layout/pop_up_hanime_time_picker.xml",
    "content": "<?xml version=\"1.0\" encoding=\"utf-8\"?>\n<LinearLayout xmlns:android=\"http://schemas.android.com/apk/res/android\"\n    android:layout_width=\"match_parent\"\n    android:layout_height=\"wrap_content\"\n    android:orientation=\"vertical\">\n\n    <com.google.android.material.materialswitch.MaterialSwitch\n        android:id=\"@+id/year_switch\"\n        android:layout_width=\"match_parent\"\n        android:layout_height=\"match_parent\"\n        android:checked=\"false\"\n        android:padding=\"16dp\"\n        android:text=\"@string/only_year\" />\n\n    <LinearLayout\n        android:layout_width=\"match_parent\"\n        android:layout_height=\"wrap_content\"\n        android:orientation=\"horizontal\"\n        android:gravity=\"center\"\n        android:padding=\"16dp\">\n        <NumberPicker\n            android:id=\"@+id/year\"\n            android:layout_width=\"wrap_content\"\n            android:layout_height=\"wrap_content\"\n            android:layout_marginHorizontal=\"8dp\" />\n        <TextView\n            android:id=\"@+id/year_text\"\n            android:layout_width=\"wrap_content\"\n            android:layout_height=\"wrap_content\"\n            android:textSize=\"16sp\"\n            android:text=\"@string/year\"/>\n\n        <NumberPicker\n            android:id=\"@+id/month\"\n            android:layout_width=\"wrap_content\"\n            android:layout_height=\"wrap_content\"\n            android:layout_marginHorizontal=\"8dp\" />\n        <TextView\n            android:id=\"@+id/month_text\"\n            android:layout_width=\"wrap_content\"\n            android:layout_height=\"wrap_content\"\n            android:textSize=\"16sp\"\n            android:text=\"@string/month\"/>\n\n        <NumberPicker\n            android:id=\"@+id/day\"\n            android:layout_width=\"wrap_content\"\n            android:layout_height=\"wrap_content\"\n            android:layout_marginHorizontal=\"8dp\" />\n        <TextView\n            android:id=\"@+id/day_text\"\n            android:layout_width=\"wrap_content\"\n            android:layout_height=\"wrap_content\"\n            android:textSize=\"16sp\"\n            android:text=\"@string/day\"/>\n\n    </LinearLayout>\n\n\n</LinearLayout>"
  },
  {
    "path": "app/src/main/res/layout/pop_up_reply.xml",
    "content": "<?xml version=\"1.0\" encoding=\"utf-8\"?>\n<LinearLayout 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=\"wrap_content\"\n    android:background=\"?android:colorBackground\"\n    android:orientation=\"horizontal\"\n    android:padding=\"4dp\">\n\n    <EditText\n        android:id=\"@+id/et_comment\"\n        android:layout_width=\"0dp\"\n        android:layout_height=\"wrap_content\"\n        android:layout_marginStart=\"4dp\"\n        android:layout_weight=\"1\"\n        android:background=\"@null\"\n        android:importantForAutofill=\"no\"\n        android:inputType=\"text\" />\n\n    <com.google.android.material.button.MaterialButton\n        android:id=\"@+id/btn_send\"\n        style=\"@style/Widget.Material3.Button.IconButton\"\n        android:layout_width=\"wrap_content\"\n        android:layout_height=\"wrap_content\"\n        app:icon=\"@drawable/ic_baseline_send_24\" />\n\n</LinearLayout>"
  },
  {
    "path": "app/src/main/res/layout-sw600dp/activity_main.xml",
    "content": "<?xml version=\"1.0\" encoding=\"utf-8\"?>\n<layout 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\n    <data>\n\n    </data>\n\n    <androidx.constraintlayout.widget.ConstraintLayout\n        android:id=\"@+id/dl_main\"\n        android:layout_width=\"match_parent\"\n        android:layout_height=\"match_parent\"\n        android:orientation=\"horizontal\">\n\n        <com.google.android.material.navigationrail.NavigationRailView\n            android:id=\"@+id/nv_main\"\n            android:layout_width=\"0dp\"\n            android:layout_height=\"match_parent\"\n            android:fitsSystemWindows=\"true\"\n            app:layout_constraintStart_toStartOf=\"parent\"\n            app:layout_constraintTop_toTopOf=\"parent\"\n            app:layout_constraintBottom_toBottomOf=\"parent\"\n            app:headerLayout=\"@layout/nav_header_ability\"\n            app:menu=\"@menu/menu_main_rail_nv\" />\n\n        <androidx.fragment.app.FragmentContainerView\n            android:id=\"@+id/fcv_main\"\n            android:name=\"androidx.navigation.fragment.NavHostFragment\"\n            android:layout_width=\"0dp\"\n            android:layout_height=\"0dp\"\n            android:fitsSystemWindows=\"true\"\n            app:defaultNavHost=\"true\"\n            app:layout_behavior=\"@string/appbar_scrolling_view_behavior\"\n            app:layout_constraintStart_toEndOf=\"@id/nv_main\"\n            app:layout_constraintEnd_toEndOf=\"parent\"\n            app:layout_constraintTop_toTopOf=\"parent\"\n            app:layout_constraintBottom_toBottomOf=\"parent\"\n            app:navGraph=\"@navigation/nav_main\" />\n\n    </androidx.constraintlayout.widget.ConstraintLayout>\n\n</layout>"
  },
  {
    "path": "app/src/main/res/layout-sw600dp/fragment_home_page.xml",
    "content": "<?xml version=\"1.0\" encoding=\"utf-8\"?>\n<layout 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\n    <data>\n\n    </data>\n\n    <androidx.coordinatorlayout.widget.CoordinatorLayout\n        android:id=\"@+id/coordinator\"\n        android:layout_width=\"match_parent\"\n        android:layout_height=\"match_parent\"\n        android:fitsSystemWindows=\"true\">\n\n        <com.google.android.material.appbar.AppBarLayout\n            android:id=\"@+id/app_bar\"\n            android:layout_width=\"match_parent\"\n            android:layout_height=\"wrap_content\"\n            android:fitsSystemWindows=\"true\">\n\n            <com.google.android.material.appbar.CollapsingToolbarLayout\n                android:id=\"@+id/collapsing_toolbar\"\n                android:layout_width=\"match_parent\"\n                android:layout_height=\"240dp\"\n                android:fitsSystemWindows=\"true\"\n                app:layout_scrollFlags=\"exitUntilCollapsed|scroll\"\n                app:titleEnabled=\"false\">\n\n                <androidx.constraintlayout.widget.ConstraintLayout\n                    android:layout_width=\"match_parent\"\n                    android:layout_height=\"match_parent\"\n                    android:fitsSystemWindows=\"true\">\n\n                    <ImageView\n                        android:id=\"@+id/cover\"\n                        android:layout_width=\"match_parent\"\n                        android:layout_height=\"match_parent\"\n                        android:contentDescription=\"@null\"\n                        android:fitsSystemWindows=\"true\"\n                        android:scaleType=\"centerCrop\"\n                        app:layout_collapseMode=\"parallax\" />\n\n                    <View\n                        android:layout_width=\"match_parent\"\n                        android:layout_height=\"match_parent\"\n                        android:background=\"@color/per60_transparent_black\" />\n\n                    <LinearLayout\n                        android:id=\"@+id/banner\"\n                        android:layout_width=\"match_parent\"\n                        android:layout_height=\"wrap_content\"\n                        android:baselineAligned=\"false\"\n                        android:gravity=\"bottom\"\n                        android:orientation=\"horizontal\"\n                        android:paddingBottom=\"8dp\"\n                        android:visibility=\"gone\"\n                        app:layout_constraintBottom_toBottomOf=\"parent\"\n                        tools:visibility=\"visible\">\n\n                        <LinearLayout\n                            android:layout_width=\"0dp\"\n                            android:layout_height=\"wrap_content\"\n                            android:layout_weight=\"1\"\n                            android:orientation=\"vertical\">\n\n                            <LinearLayout\n                                android:layout_width=\"match_parent\"\n                                android:layout_height=\"wrap_content\">\n\n                                <View\n                                    android:id=\"@+id/a_color\"\n                                    android:layout_width=\"8dp\"\n                                    android:layout_height=\"match_parent\"\n                                    android:layout_marginVertical=\"4dp\"\n                                    android:background=\"@color/red\" />\n\n                                <TextView\n                                    android:id=\"@+id/tv_banner_title\"\n                                    style=\"@style/TextAppearance.Material3.TitleLarge\"\n                                    android:layout_width=\"wrap_content\"\n                                    android:layout_height=\"wrap_content\"\n                                    android:layout_marginStart=\"4dp\"\n                                    android:ellipsize=\"end\"\n                                    android:maxLines=\"1\"\n                                    android:paddingVertical=\"4dp\"\n                                    android:textAlignment=\"center\"\n                                    android:textColor=\"@color/white\"\n                                    android:textStyle=\"bold\"\n                                    tools:text=\"1234 -v 125\" />\n\n                            </LinearLayout>\n\n                            <TextView\n                                android:id=\"@+id/tv_banner_desc\"\n                                android:layout_width=\"wrap_content\"\n                                android:layout_height=\"wrap_content\"\n                                android:layout_marginStart=\"10dp\"\n                                android:layout_marginTop=\"2dp\"\n                                android:ellipsize=\"end\"\n                                android:maxLines=\"2\"\n                                android:padding=\"4dp\"\n                                tools:text=\"131231313566666666666624444444444444444466666\" />\n\n                        </LinearLayout>\n\n                        <FrameLayout\n                            android:id=\"@+id/btn_banner\"\n                            android:layout_width=\"64dp\"\n                            android:layout_height=\"40dp\"\n                            android:background=\"@android:color/transparent\">\n\n                            <ImageView\n                                android:id=\"@+id/iv_banner\"\n                                android:layout_width=\"wrap_content\"\n                                android:layout_height=\"wrap_content\"\n                                android:layout_gravity=\"center|end\"\n                                android:layout_marginEnd=\"4dp\"\n                                android:background=\"@drawable/ic_baseline_arrow_forward_24\"\n                                android:backgroundTint=\"@color/red\"\n                                android:contentDescription=\"@string/enter\" />\n\n                        </FrameLayout>\n                        <!--\n\n                        <com.google.android.material.button.MaterialButton\n                            android:id=\"@+id/btn_banner\"\n                            style=\"@style/Widget.Material3.Button.IconButton\"\n                            android:layout_width=\"wrap_content\"\n                            android:layout_height=\"wrap_content\"\n                            android:layout_marginHorizontal=\"8dp\"\n                            android:backgroundTint=\"@color/per70_transparent_white\"\n                            app:icon=\"@drawable/ic_baseline_arrow_forward_24\"\n                            app:iconTint=\"@color/red\" />\n                            -->\n\n                    </LinearLayout>\n\n                </androidx.constraintlayout.widget.ConstraintLayout>\n\n                <androidx.appcompat.widget.Toolbar\n                    android:id=\"@+id/toolbar\"\n                    android:layout_width=\"match_parent\"\n                    android:layout_height=\"?attr/actionBarSize\"\n                    app:layout_collapseMode=\"pin\"\n                    app:menu=\"@menu/menu_main_toolbar\">\n\n                </androidx.appcompat.widget.Toolbar>\n\n            </com.google.android.material.appbar.CollapsingToolbarLayout>\n\n        </com.google.android.material.appbar.AppBarLayout>\n\n        <com.scwang.smart.refresh.layout.SmartRefreshLayout\n            android:id=\"@+id/home_page_srl\"\n            android:layout_width=\"match_parent\"\n            android:layout_height=\"match_parent\"\n            app:layout_behavior=\"@string/appbar_scrolling_view_behavior\">\n\n            <com.scwang.smart.refresh.header.MaterialHeader\n                android:id=\"@+id/header\"\n                android:layout_width=\"match_parent\"\n                android:layout_height=\"wrap_content\" />\n\n            <com.drake.statelayout.StateLayout\n                android:id=\"@+id/state\"\n                android:layout_width=\"match_parent\"\n                android:layout_height=\"match_parent\">\n\n                <com.yenaly.yenaly_libs.base.view.RecyclerViewAtViewPager2\n                    android:id=\"@+id/rv\"\n                    android:layout_width=\"match_parent\"\n                    android:layout_height=\"match_parent\"\n                    app:layoutManager=\"androidx.recyclerview.widget.GridLayoutManager\"\n                    app:spanCount=\"2\" />\n\n            </com.drake.statelayout.StateLayout>\n\n        </com.scwang.smart.refresh.layout.SmartRefreshLayout>\n\n    </androidx.coordinatorlayout.widget.CoordinatorLayout>\n\n</layout>"
  },
  {
    "path": "app/src/main/res/menu/menu_downloaded_toolbar.xml",
    "content": "<?xml version=\"1.0\" encoding=\"utf-8\"?>\n<menu xmlns:app=\"http://schemas.android.com/apk/res-auto\"\n    xmlns:android=\"http://schemas.android.com/apk/res/android\">\n\n    <item\n        android:id=\"@+id/tb_sort\"\n        android:icon=\"@drawable/baseline_sort_24\"\n        android:title=\"@string/sort_option\"\n        app:showAsAction=\"ifRoom\">\n\n        <menu>\n\n            <group android:checkableBehavior=\"single\">\n\n                <item\n                    android:id=\"@+id/sm_sort_by_date_ascending\"\n                    android:title=\"@string/sort_by_date_ascending\" />\n\n                <item\n                    android:id=\"@+id/sm_sort_by_date_descending\"\n                    android:checked=\"true\"\n                    android:title=\"@string/sort_by_date_descending\" />\n\n                <item\n                    android:id=\"@+id/sm_sort_by_alphabet_ascending\"\n                    android:title=\"@string/sort_by_alphabet_ascending\" />\n\n                <item\n                    android:id=\"@+id/sm_sort_by_alphabet_descending\"\n                    android:title=\"@string/sort_by_alphabet_descending\" />\n\n            </group>\n\n        </menu>\n\n    </item>\n\n</menu>"
  },
  {
    "path": "app/src/main/res/menu/menu_downloading_toolbar.xml",
    "content": "<?xml version=\"1.0\" encoding=\"utf-8\"?>\n<menu xmlns:android=\"http://schemas.android.com/apk/res/android\"\n    xmlns:app=\"http://schemas.android.com/apk/res-auto\">\n    <item\n        android:id=\"@+id/tb_test\"\n        android:icon=\"@drawable/ic_baseline_download_24\"\n        android:title=\"test\"\n        android:visible=\"true\"\n        app:showAsAction=\"always\" />\n    <item\n        android:id=\"@+id/tb_start_all\"\n        android:icon=\"@drawable/ic_baseline_play_arrow_24_tintwhite\"\n        android:title=\"@string/start_all\"\n        app:showAsAction=\"always\" />\n    <item\n        android:id=\"@+id/tb_pause_all\"\n        android:icon=\"@drawable/ic_baseline_pause_24_tintwhite\"\n        android:title=\"@string/pause_all\"\n        app:showAsAction=\"always\" />\n</menu>"
  },
  {
    "path": "app/src/main/res/menu/menu_h_keyframes_toolbar.xml",
    "content": "<?xml version=\"1.0\" encoding=\"utf-8\"?>\n<menu xmlns:android=\"http://schemas.android.com/apk/res/android\"\n    xmlns:app=\"http://schemas.android.com/apk/res-auto\">\n\n    <item\n        android:id=\"@+id/tb_add\"\n        android:icon=\"@drawable/baseline_add_24\"\n        app:showAsAction=\"ifRoom\"\n        android:title=\"@string/add\" />\n</menu>"
  },
  {
    "path": "app/src/main/res/menu/menu_main_bnv.xml",
    "content": "<?xml version=\"1.0\" encoding=\"utf-8\"?>\n<menu xmlns:android=\"http://schemas.android.com/apk/res/android\">\n\n    <item\n        android:id=\"@+id/bnv_home\"\n        android:icon=\"@drawable/ic_baseline_home_24\"\n        android:title=\"@string/home_page\" />\n\n</menu>"
  },
  {
    "path": "app/src/main/res/menu/menu_main_nv.xml",
    "content": "<?xml version=\"1.0\" encoding=\"utf-8\"?>\n<menu xmlns:android=\"http://schemas.android.com/apk/res/android\"\n    xmlns:tools=\"http://schemas.android.com/tools\"\n    tools:showIn=\"navigation_view\">\n\n    <group android:checkableBehavior=\"single\">\n        <item\n            android:id=\"@+id/nv_home_page\"\n            android:icon=\"@drawable/ic_baseline_home_24\"\n            android:title=\"@string/home_page\" />\n        <item\n            android:id=\"@+id/nv_settings\"\n            android:icon=\"@drawable/ic_baseline_settings_24\"\n            android:title=\"@string/settings\" />\n        <item\n            android:id=\"@+id/nv_h_keyframe_settings\"\n            android:icon=\"@drawable/baseline_h_24\"\n            android:title=\"@string/h_keyframe_settings\" />\n\n    </group>\n\n    <item android:title=\"@string/my_list\">\n        <menu>\n            <group android:checkableBehavior=\"single\">\n                <item\n                    android:id=\"@+id/nv_watch_later\"\n                    android:icon=\"@drawable/ic_baseline_watch_later_24\"\n                    android:title=\"@string/watch_later\" />\n                <item\n                    android:id=\"@+id/nv_fav_video\"\n                    android:icon=\"@drawable/ic_baseline_favorite_24\"\n                    android:title=\"@string/fav_video\" />\n                <item\n                    android:id=\"@+id/nv_playlist\"\n                    android:icon=\"@drawable/ic_baseline_list_24\"\n                    android:title=\"@string/play_list\" />\n            </group>\n        </menu>\n    </item>\n\n    <item android:title=\"@string/video\">\n        <menu>\n            <group android:checkableBehavior=\"single\">\n                <item\n                    android:id=\"@+id/nv_watch_history\"\n                    android:icon=\"@drawable/ic_baseline_history_24\"\n                    android:title=\"@string/watch_history\" />\n                <item\n                    android:id=\"@+id/nv_download\"\n                    android:icon=\"@drawable/ic_baseline_download_24\"\n                    android:title=\"@string/download\" />\n            </group>\n        </menu>\n    </item>\n</menu>"
  },
  {
    "path": "app/src/main/res/menu/menu_main_rail_nv.xml",
    "content": "<?xml version=\"1.0\" encoding=\"utf-8\"?>\n<menu xmlns:android=\"http://schemas.android.com/apk/res/android\"\n    xmlns:tools=\"http://schemas.android.com/tools\"\n    tools:showIn=\"navigation_view\">\n\n    <item\n        android:id=\"@+id/nv_home_page\"\n        android:enabled=\"true\"\n        android:icon=\"@drawable/ic_baseline_home_24\"\n        android:title=\"@string/home_page\" />\n    <item\n        android:id=\"@+id/nv_settings\"\n        android:enabled=\"true\"\n        android:icon=\"@drawable/ic_baseline_settings_24\"\n        android:title=\"@string/settings\" />\n    <item\n        android:id=\"@+id/nv_h_keyframe_settings\"\n        android:enabled=\"true\"\n        android:icon=\"@drawable/baseline_h_24\"\n        android:title=\"@string/h_keyframe_settings\" />\n\n    <item\n        android:id=\"@+id/alarms\"\n        android:title=\"@string/my_list\">\n        <menu>\n            <item\n                android:id=\"@+id/nv_watch_later\"\n                android:enabled=\"true\"\n                android:icon=\"@drawable/ic_baseline_watch_later_24\"\n                android:title=\"@string/watch_later\" />\n            <item\n                android:id=\"@+id/nv_fav_video\"\n                android:enabled=\"true\"\n                android:icon=\"@drawable/ic_baseline_favorite_24\"\n                android:title=\"@string/fav_video\" />\n            <item\n                android:id=\"@+id/nv_playlist\"\n                android:enabled=\"true\"\n                android:icon=\"@drawable/ic_baseline_list_24\"\n                android:title=\"@string/play_list\" />\n        </menu>\n    </item>\n\n    <item android:title=\"@string/video\">\n        <menu>\n            <item\n                android:id=\"@+id/nv_watch_history\"\n                android:enabled=\"true\"\n                android:icon=\"@drawable/ic_baseline_history_24\"\n                android:title=\"@string/watch_history\" />\n            <item\n                android:id=\"@+id/nv_download\"\n                android:enabled=\"true\"\n                android:icon=\"@drawable/ic_baseline_download_24\"\n                android:title=\"@string/download\" />\n        </menu>\n    </item>\n</menu>"
  },
  {
    "path": "app/src/main/res/menu/menu_main_toolbar.xml",
    "content": "<?xml version=\"1.0\" encoding=\"utf-8\"?>\n<menu xmlns:android=\"http://schemas.android.com/apk/res/android\"\n    xmlns:app=\"http://schemas.android.com/apk/res-auto\">\n\n    <item\n        android:id=\"@+id/tb_previews\"\n        android:icon=\"@drawable/ic_baseline_newspaper_24\"\n        android:title=\"@string/hanime_list\"\n        app:showAsAction=\"ifRoom\" />\n\n    <item\n        android:id=\"@+id/tb_search\"\n        android:icon=\"@drawable/ic_baseline_search_24\"\n        android:title=\"@string/search\"\n        app:showAsAction=\"ifRoom\" />\n\n</menu>"
  },
  {
    "path": "app/src/main/res/menu/menu_my_list_toolbar.xml",
    "content": "<?xml version=\"1.0\" encoding=\"utf-8\"?>\n<menu xmlns:android=\"http://schemas.android.com/apk/res/android\"\n    xmlns:app=\"http://schemas.android.com/apk/res-auto\">\n    <item\n        android:id=\"@+id/tb_help\"\n        android:icon=\"@drawable/ic_baseline_help_24\"\n        app:showAsAction=\"ifRoom\"\n        android:title=\"@string/help\" />\n</menu>"
  },
  {
    "path": "app/src/main/res/menu/menu_playlist_toolbar.xml",
    "content": "<?xml version=\"1.0\" encoding=\"utf-8\"?>\n<menu xmlns:android=\"http://schemas.android.com/apk/res/android\"\n    xmlns:app=\"http://schemas.android.com/apk/res-auto\">\n    <item\n        android:id=\"@+id/tb_open_drawer\"\n        android:icon=\"@drawable/baseline_format_list_bulleted_24\"\n        app:showAsAction=\"ifRoom\"\n        android:title=\"@string/open_playlist_drawer\" />\n</menu>"
  },
  {
    "path": "app/src/main/res/menu/menu_preview_toolbar.xml",
    "content": "<?xml version=\"1.0\" encoding=\"utf-8\"?>\n<menu xmlns:android=\"http://schemas.android.com/apk/res/android\"\n    xmlns:app=\"http://schemas.android.com/apk/res-auto\">\n\n    <item\n        android:id=\"@+id/tb_comment\"\n        android:icon=\"@drawable/ic_baseline_comment_24\"\n        android:title=\"@string/comment\"\n        app:showAsAction=\"ifRoom\" />\n\n</menu>"
  },
  {
    "path": "app/src/main/res/menu/menu_search_toolbar.xml",
    "content": "<?xml version=\"1.0\" encoding=\"utf-8\"?>\n<menu xmlns:android=\"http://schemas.android.com/apk/res/android\"\n    xmlns:app=\"http://schemas.android.com/apk/res-auto\">\n    <item\n        android:id=\"@+id/tb_cancel_all_tags\"\n        android:icon=\"@drawable/ic_baseline_cancel_24\"\n        android:title=\"@string/cancel_all_tags\"\n        app:showAsAction=\"always\" />\n</menu>"
  },
  {
    "path": "app/src/main/res/menu/menu_watch_history_toolbar.xml",
    "content": "<?xml version=\"1.0\" encoding=\"utf-8\"?>\n<menu xmlns:android=\"http://schemas.android.com/apk/res/android\"\n    xmlns:app=\"http://schemas.android.com/apk/res-auto\">\n\n    <item\n        android:id=\"@+id/tb_delete\"\n        android:icon=\"@drawable/ic_baseline_delete_24\"\n        app:showAsAction=\"ifRoom\"\n        android:title=\"@string/delete\" />\n\n    <item\n        android:id=\"@+id/tb_help\"\n        android:icon=\"@drawable/ic_baseline_help_24\"\n        app:showAsAction=\"ifRoom\"\n        android:title=\"@string/help\" />\n\n</menu>"
  },
  {
    "path": "app/src/main/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/ic_launcher_background\"/>\n    <foreground android:drawable=\"@mipmap/ic_launcher_foreground\"/>\n</adaptive-icon>"
  },
  {
    "path": "app/src/main/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/ic_launcher_background\"/>\n    <foreground android:drawable=\"@mipmap/ic_launcher_foreground\"/>\n</adaptive-icon>"
  },
  {
    "path": "app/src/main/res/navigation/nav_main.xml",
    "content": "<?xml version=\"1.0\" encoding=\"utf-8\"?>\n<navigation xmlns:android=\"http://schemas.android.com/apk/res/android\"\n    xmlns:app=\"http://schemas.android.com/apk/res-auto\"\n    android:id=\"@+id/nav_main\"\n    app:startDestination=\"@id/nv_home_page\">\n\n    <fragment\n        android:id=\"@id/nv_home_page\"\n        android:name=\"com.yenaly.han1meviewer.ui.fragment.home.HomePageFragment\" />\n    <fragment\n        android:id=\"@id/nv_watch_history\"\n        android:name=\"com.yenaly.han1meviewer.ui.fragment.home.WatchHistoryFragment\" />\n    <fragment\n        android:id=\"@id/nv_fav_video\"\n        android:name=\"com.yenaly.han1meviewer.ui.fragment.home.MyFavVideoFragment\" />\n    <fragment\n        android:id=\"@id/nv_playlist\"\n        android:name=\"com.yenaly.han1meviewer.ui.fragment.home.MyPlaylistFragment\" />\n    <fragment\n        android:id=\"@id/nv_watch_later\"\n        android:name=\"com.yenaly.han1meviewer.ui.fragment.home.MyWatchLaterFragment\" />\n\n</navigation>"
  },
  {
    "path": "app/src/main/res/navigation/nav_settings.xml",
    "content": "<?xml version=\"1.0\" encoding=\"utf-8\"?>\n<navigation xmlns:android=\"http://schemas.android.com/apk/res/android\"\n    xmlns:app=\"http://schemas.android.com/apk/res-auto\"\n    android:id=\"@+id/nav_settings\"\n    app:startDestination=\"@id/homeSettingsFragment\">\n\n    <fragment\n        android:id=\"@+id/homeSettingsFragment\"\n        android:name=\"com.yenaly.han1meviewer.ui.fragment.settings.HomeSettingsFragment\" />\n\n    <fragment\n        android:id=\"@+id/playerSettingsFragment\"\n        android:name=\"com.yenaly.han1meviewer.ui.fragment.settings.PlayerSettingsFragment\" />\n\n    <fragment\n        android:id=\"@+id/hKeyframesFragment\"\n        android:name=\"com.yenaly.han1meviewer.ui.fragment.settings.HKeyframesFragment\" />\n\n    <fragment\n        android:id=\"@+id/sharedHKeyframesFragment\"\n        android:name=\"com.yenaly.han1meviewer.ui.fragment.settings.SharedHKeyframesFragment\" />\n\n    <fragment\n        android:id=\"@+id/hKeyframeSettingsFragment\"\n        android:name=\"com.yenaly.han1meviewer.ui.fragment.settings.HKeyframeSettingsFragment\" />\n\n    <fragment\n        android:id=\"@+id/networkSettingsFragment\"\n        android:name=\"com.yenaly.han1meviewer.ui.fragment.settings.NetworkSettingsFragment\" />\n\n    <fragment\n        android:id=\"@+id/downloadSettingsFragment\"\n        android:name=\"com.yenaly.han1meviewer.ui.fragment.settings.DownloadSettingsFragment\" />\n\n</navigation>"
  },
  {
    "path": "app/src/main/res/values/arrays.xml",
    "content": "<?xml version=\"1.0\" encoding=\"utf-8\"?>\n<resources>\n    \n</resources>"
  },
  {
    "path": "app/src/main/res/values/colors.xml",
    "content": "<?xml version=\"1.0\" encoding=\"utf-8\"?>\n<resources>\n    <color name=\"purple_200\">#FFBB86FC</color>\n    <color name=\"purple_500\">#FF6200EE</color>\n    <color name=\"purple_700\">#FF3700B3</color>\n    <color name=\"teal_200\">#FF03DAC5</color>\n    <color name=\"teal_700\">#FF018786</color>\n    <color name=\"black\">#FF000000</color>\n    <color name=\"white\">#FFFFFFFF</color>\n    <color name=\"red\">#FFFF0000</color>\n    <color name=\"grey_700\">#FF616161</color>\n\n    <color name=\"at_person\">#FFED6A2C</color>\n    <color name=\"title_mask_color\">#66F5F5F5</color>\n    <color name=\"search_bar_icon_color\">#FF00A1FF</color>\n    <color name=\"video_code_link_text_color\">#FF1E90FF</color>\n\n    <color name=\"per10_transparent_black\">#19000000</color>\n    <color name=\"per20_transparent_black\">#33000000</color>\n    <color name=\"per30_transparent_black\">#4D000000</color>\n    <color name=\"per40_transparent_black\">#66000000</color>\n    <color name=\"per45_transparent_black\">#73000000</color>\n    <color name=\"per50_transparent_black\">#80000000</color>\n    <color name=\"per60_transparent_black\">#99000000</color>\n    <color name=\"per70_transparent_black\">#B3000000</color>\n    <color name=\"per80_transparent_black\">#CC000000</color>\n    <color name=\"per90_transparent_black\">#E6000000</color>\n\n    <color name=\"per10_transparent_white\">#19FFFFFF</color>\n    <color name=\"per20_transparent_white\">#33FFFFFF</color>\n    <color name=\"per30_transparent_white\">#4DFFFFFF</color>\n    <color name=\"per40_transparent_white\">#66FFFFFF</color>\n    <color name=\"per50_transparent_white\">#80FFFFFF</color>\n    <color name=\"per60_transparent_white\">#99FFFFFF</color>\n    <color name=\"per70_transparent_white\">#B3FFFFFF</color>\n    <color name=\"per80_transparent_white\">#CCFFFFFF</color>\n    <color name=\"per90_transparent_white\">#E6FFFFFF</color>\n    <color name=\"per50_transparent_grey\">#80B0B0B0</color>\n    <color name=\"per90_dark_red\">#E6100000</color>\n    <color name=\"adv_search_unselected_color\">#FF495057</color>\n    <color name=\"adv_search_selected_color\">#FF28A745</color>\n</resources>"
  },
  {
    "path": "app/src/main/res/values/dimens.xml",
    "content": "<?xml version=\"1.0\" encoding=\"utf-8\"?>\n<resources>\n    <dimen name=\"video_cover_height\">108dp</dimen>\n    <dimen name=\"video_cover_width\">192dp</dimen>\n    <dimen name=\"video_cover_simplified_width_large\">150dp</dimen>\n    <dimen name=\"video_cover_simplified_height_large\">220dp</dimen>\n    <dimen name=\"video_cover_simplified_width\">120dp</dimen>\n    <dimen name=\"video_cover_simplified_height\">176dp</dimen>\n    <dimen name=\"video_cover_simplified_width_small\">90dp</dimen>\n    <dimen name=\"video_cover_simplified_height_small\">132dp</dimen>\n    <dimen name=\"video_cover_height_small\">72dp</dimen>\n    <dimen name=\"video_cover_weight_small\">128dp</dimen>\n    <dimen name=\"video_cover_height_medium\">96dp</dimen>\n    <dimen name=\"hanime_preview_news_pic_width\">120dp</dimen>\n    <dimen name=\"hanime_preview_news_pic_height\">90dp</dimen>\n    <dimen name=\"avatar_size\">36dp</dimen>\n    <dimen name=\"subscribe_avatar_size\">58dp</dimen>\n    <dimen name=\"subscribe_text_width\">78dp</dimen>\n\n    <!-- use for comment align -->\n    <dimen name=\"comment_content_margin\">44dp</dimen>\n    <dimen name=\"comment_button_margin\">32dp</dimen>\n\n    <dimen name=\"view_view_and_time_icon_size\">14dp</dimen>\n    <dimen name=\"video_view_and_time_and_duration\">12sp</dimen>\n    <dimen name=\"err_tip_text_size\">24sp</dimen>\n    <dimen name=\"main_avatar_size\">48dp</dimen>\n    <dimen name=\"main_expanded_avatar_size\">90dp</dimen>\n</resources>"
  },
  {
    "path": "app/src/main/res/values/ic_launcher_background.xml",
    "content": "<?xml version=\"1.0\" encoding=\"utf-8\"?>\n<resources>\n    <color name=\"ic_launcher_background\">#212121</color>\n</resources>"
  },
  {
    "path": "app/src/main/res/values/ids.xml",
    "content": "<?xml version=\"1.0\" encoding=\"utf-8\"?>\n<resources>\n    <item name=\"click_condition\" type=\"id\" />\n</resources>"
  },
  {
    "path": "app/src/main/res/values/strings.xml",
    "content": "<resources xmlns:tools=\"http://schemas.android.com/tools\">\n\n    <string name=\"hanime_app_name\" translatable=\"false\" tools:ignore=\"Typos\">Han1meViewer</string>\n    <string name=\"home_page\">首頁</string>\n    <string name=\"h_anime\">H動漫</string>\n    <string name=\"latest_hanime\">最新裏番</string>\n    <string name=\"fresh\">新鮮</string>\n    <string name=\"latest_upload\">最新上傳</string>\n    <string name=\"this_month\">本月</string>\n    <string name=\"hot_video\">發燒影片</string>\n    <string name=\"current\">當下</string>\n    <string name=\"hot_video_2\">熱門影片</string>\n    <string name=\"trends\">動態</string>\n    <string name=\"they_watched\">他們在看</string>\n    <string name=\"search\">搜尋</string>\n    <string name=\"advanced_search\">高級搜尋</string>\n    <string name=\"back\">返回</string>\n    <string name=\"input_keyword\">請輸入關鍵字</string>\n    <string name=\"type\">類型</string>\n    <string name=\"tag\">標籖</string>\n    <string name=\"sort_option\">排序方式</string>\n    <string name=\"brand\">品牌</string>\n    <string name=\"release_date\">發佈日期</string>\n    <string name=\"duration\">片長</string>\n    <string name=\"duration_able_reset\">片長（複選可取消）</string>\n    <string name=\"type_able_reset\">類型（複選可取消）</string>\n    <string name=\"sort_option_able_reset\">排序方式（複選可取消）</string>\n    <string name=\"cancel_all_tags\">取消所有tag</string>\n    <string name=\"alert\">提示</string>\n    <string name=\"alert_cancel_all_tags\">確認將會取消你所選的所有Tag</string>\n    <string name=\"confirm\">確認</string>\n    <string name=\"cancel\">取消</string>\n    <string name=\"content_tag\">內容標籤</string>\n    <string name=\"pair_widely\">廣泛配對</string>\n    <string name=\"pair_widely_alert\">較多結果，較不精準。配對所有包含任何一個選擇的標籤的影片，而非全部標籤</string>\n    <string name=\"reset\">重設</string>\n    <string name=\"save\">保存</string>\n    <string name=\"save_and_search\">保存並搜尋</string>\n    <string name=\"video_attr\">影片屬性</string>\n    <string name=\"relationship\">人物關係</string>\n    <string name=\"characteristics\">角色設定</string>\n    <string name=\"appearance_and_figure\">外貌身材</string>\n    <string name=\"story_plot\">故事劇情</string>\n    <string name=\"sex_position\">性交體位</string>\n    <string name=\"reset_date\">重設日期</string>\n    <string name=\"copy_to_clipboard\">已複製到剪貼簿</string>\n    <string name=\"introduction\">簡介</string>\n    <string name=\"comment\">評論</string>\n    <string name=\"collapse\">收起</string>\n    <string name=\"expand\">展開</string>\n    <string name=\"series_video\">系列影片</string>\n    <string name=\"related_video\">相關影片</string>\n    <string name=\"now_playing\">現正播放</string>\n    <string name=\"view_more_replies\">查看更多回覆</string>\n    <string name=\"comment_not_found\">未發現評論</string>\n    <string name=\"hanime_list\">新番列表</string>\n    <string name=\"latest_hanime_tour\">新番導覽</string>\n    <string name=\"latest_hanime_list_monthly\">%s 裏番新番列表</string>\n    <string name=\"latest_hanime_news\">新番資訊</string>\n    <string name=\"latest_hanime_comment\">%s 新番評論區</string>\n    <string name=\"my_list\">我的清單</string>\n    <string name=\"watch_later\">稍後觀看</string>\n    <string name=\"fav_video\">喜歡的影片</string>\n    <string name=\"settings\">設定</string>\n    <string name=\"video\">影片</string>\n    <string name=\"download\">下載</string>\n    <string name=\"watch_history\">觀看歷史</string>\n    <string name=\"login\">登入</string>\n    <string name=\"delete\">刪除</string>\n    <string name=\"here_is_empty\">這裡空空的…</string>\n    <string name=\"play_list\">播放清單</string>\n    <string name=\"add_to_fav\">加入喜歡</string>\n    <string name=\"share\">分享</string>\n    <string name=\"add_to_watch_later\">加入待看</string>\n    <string name=\"video_language\">影片語言</string>\n    <string name=\"preference\">偏好</string>\n    <string name=\"other\">其他</string>\n    <string name=\"about\">關於</string>\n    <string name=\"check_update\">檢查更新</string>\n    <string name=\"new_version_found\">已發現新版本</string>\n    <string name=\"checking_update\">檢查更新中…</string>\n    <string name=\"update_content\">更新內容</string>\n    <string name=\"check_update_failed\">檢查更新失敗，點擊重試</string>\n    <string name=\"check_update_success\">當前有新版本：%s，點擊查看</string>\n    <string name=\"current_version\">當前版本：%s</string>\n    <string name=\"already_latest_update\">已經是最新版本！</string>\n    <string name=\"app_slogan\">非官方 Hanime1.me 用戶端</string>\n    <string name=\"search_placeholder\">今天想看點什麼呢</string>\n    <string name=\"downloading\">正在下載</string>\n    <string name=\"downloading_s\">正在下載：%1$s</string>\n    <string name=\"downloading_update_percent\">正在下載更新 %d%%</string>\n    <string name=\"downloaded\">已下載</string>\n    <string name=\"start_download\">開始下載</string>\n    <string name=\"cancel_download\">取消下載</string>\n    <string name=\"local_playback\">本地觀看</string>\n    <string name=\"download_path\">下載路徑</string>\n    <string name=\"help\">幫助</string>\n    <string name=\"reply\">回覆</string>\n    <string name=\"reply_child_comment\">回覆子評論</string>\n    <string name=\"comment_video\">評論本影片</string>\n    <string name=\"login_first\">請先登入</string>\n    <string name=\"reply_warning\">（輸入欄消失後輸入文字也會消失）</string>\n    <string name=\"clear_cache\">清除快取</string>\n    <string name=\"cache_occupy\">快取佔用</string>\n    <string name=\"cache_usage_summary\"><![CDATA[目前佔用了 <b>%1$s</b> 的儲存空間]]></string>\n    <string name=\"allow\">允許</string>\n    <string name=\"deny\">拒絕</string>\n    <string name=\"no_video_links_found\">未發現影片連結</string>\n    <string name=\"child_comment\">子評論</string>\n    <string name=\"search_options_tips\">長按某單元可以清除當前你所選單元的所有Tag</string>\n    <string name=\"pause\">暫停</string>\n    <string name=\"continues\">繼續</string>\n    <string name=\"jump_to_webpage\">轉到網頁</string>\n    <string name=\"sure_to_delete_s\">確定要刪除 %s 嗎？</string>\n    <string name=\"sure_to_delete\">確定要刪除嗎？</string>\n    <string name=\"prepare_to_delete_s\">你現在正要準備刪除\\n%s</string>\n    <string name=\"playlist_title\">標題</string>\n    <string name=\"playlist_description\">介紹</string>\n    <string name=\"open_playlist_drawer\">打開播放清單列表</string>\n    <string name=\"add_to_playlist\">加入清單</string>\n    <string name=\"player_settings\">播放器設定</string>\n    <string name=\"current_slide_sensitivity\">當前靈敏度：%s</string>\n    <string name=\"network\">網路</string>\n    <string name=\"safe_dns\">安全 DNS</string>\n    <string name=\"proxy\">代理</string>\n    <string name=\"host_or_ip\">Host 或 IP</string>\n    <string name=\"port\">埠</string>\n    <string name=\"direct\">直接連接</string>\n    <string name=\"system_proxy\">系統代理</string>\n    <string name=\"http\" translatable=\"false\">HTTP</string>\n    <string name=\"http_proxy\" translatable=\"false\">HTTP %1$s:%2$d</string>\n    <string name=\"socks\" translatable=\"false\">SOCKS</string>\n    <string name=\"socks_proxy\" translatable=\"false\">SOCKS %1$s:%2$d</string>\n    <string name=\"host_or_ipv4\">Host 或 IPv4</string>\n    <string name=\"sort_by_date_ascending\">時間正排序</string>\n    <string name=\"sort_by_date_descending\">時間反排序</string>\n    <string name=\"sort_by_alphabet_ascending\">字母正排序</string>\n    <string name=\"sort_by_alphabet_descending\">字母反排序</string>\n    <string name=\"h_keyframe_settings\">關鍵H幀設定</string>\n    <string name=\"h_keyframes_enable\">關鍵H幀，啟動！</string>\n    <string name=\"h_keyframes_enable_tip\">該功能關閉後並不會刪除你的歷史資料</string>\n    <string name=\"h_keyframes_disable_tip\">開啟後，播放器頂部會顯示🥵</string>\n    <string name=\"h_keyframe_title_prefix\">影片代號：</string>\n    <string name=\"manage\">管理</string>\n    <string name=\"h_keyframe_manage\">關鍵H幀管理</string>\n    <string name=\"h_keyframe\">關鍵H幀</string>\n    <string name=\"when_countdown_remind\">何時開始倒數計時提醒</string>\n    <string name=\"show_prompt_when_countdown\">倒數計時顯示提示</string>\n    <string name=\"will_remind_before_d_seconds\">將會在 %d 秒前倒數計時提醒</string>\n    <string name=\"shared_h_keyframes_enable\">採用共享關鍵H幀集</string>\n    <string name=\"shared_h_keyframe_manage\">共享關鍵H幀管理</string>\n    <string name=\"shared_h_keyframe_manage_tip\">歡迎到 GitHub 上面提交你的關鍵H幀，如果不會提交，討論區有教學文供參考</string>\n    <string name=\"shared_h_keyframes_enable_tip\">採用好心人上傳到 Github 的關鍵H幀集</string>\n    <string name=\"shared\">共享</string>\n    <string name=\"shared_h_keyframes_use_first\">優先採用共享關鍵H幀集</string>\n    <string name=\"shared_h_keyframes_use_first_tip\">當共享集和本地集都存在某一影片的關鍵H幀時，優先採用共享集</string>\n    <string name=\"forum\">討論區</string>\n    <string name=\"forum_summary\">歡迎來吐槽，如果有重要資訊也會發佈到這裡</string>\n    <string name=\"submit_bug\">提交 BUG</string>\n    <string name=\"submit_bug_summary\">出 BUG 了就來提交個 issue 吧</string>\n    <string name=\"position_ms\">位置 (ms)</string>\n    <string name=\"prompt\">提示</string>\n    <string name=\"modify_h_keyframe\">修改關鍵H幀</string>\n    <string name=\"edit\">修改</string>\n    <string name=\"title\">標題</string>\n    <string name=\"video_code\">影片代號</string>\n    <string name=\"copy_\">複製</string>\n    <string name=\"add\">添加</string>\n    <string name=\"h_keyframes_shared_by_other_detected\">檢測到他人分享的關鍵H幀</string>\n    <string name=\"h_keyframes_shared_by_other_not_detected\">未檢測到他人分享的關鍵H幀</string>\n    <string name=\"share_to_others\">分享給其他人</string>\n    <string name=\"latest_release\">最新上市</string>\n    <string name=\"chinese_subtitle\">中文字幕</string>\n    <string name=\"ranking_today\">本日排行</string>\n    <string name=\"ranking_this_month\">本月排行</string>\n    <string name=\"long_press_speed_not_supported\">您的設備暫時不支援長按快轉</string>\n    <string name=\"start_all\">全部開始</string>\n    <string name=\"pause_all\">全部暫停</string>\n    <string name=\"domain_name\">域名</string>\n    <string name=\"use_built_in_hosts\">應用內建 Hosts</string>\n    <string name=\"network_settings\">網路設定</string>\n    <string name=\"use_built_in_hosts_summary\">應用提供的主機到 IP 的映射\\n不在中國大陸，無需開啟該選項</string>\n    <string name=\"username\">帳號</string>\n    <string name=\"password\">密碼</string>\n    <string name=\"try_login_here\">試試在這裡登入</string>\n    <string name=\"email\">電子郵件</string>\n    <string name=\"login_failed\">登入失敗</string>\n    <string name=\"login_success\">登入成功</string>\n    <string name=\"account_or_password_wrong\">帳號或密碼可能錯誤</string>\n    <string name=\"login_tips\">該方法只能保持登入 2 個小時… 還是推薦使用網頁登入，\\n但網頁是不走 Hosts 的，所以可能需要 VPN</string>\n    <string name=\"ignore\">忽略</string>\n    <string name=\"update\">更新</string>\n    <string name=\"use_ci_update_channel\">使用 CI 更新通道。</string>\n    <string name=\"use_ci_update_channel_summary\">CI 更新頻道能更快地取得最新變更\\n但不保證穩定性</string>\n    <string name=\"update_popup_interval_days\">檢查更新跳出視窗間隔天數</string>\n    <string name=\"update_failed\">更新失敗</string>\n    <string name=\"update_download_background\">在后台下載更新</string>\n    <string name=\"allow_install_from_unknown_app_sources\">允許當前 App 來源安裝</string>\n    <string name=\"reason_for_allow_install_from_unknown_app_sources\">不用擔心，這只是為了更新。\\n你如果不放心，可以直接去 Github 上下載</string>\n    <string name=\"go_to_settings\">前往設定</string>\n    <string name=\"allow_post_notification\">允許發送通知</string>\n    <string name=\"reason_for_download_notification\">請同意通知權限，及時收到下載訊息！</string>\n    <string name=\"msg_deny_download_notification\">進行或完成時不會通知哦</string>\n    <string name=\"at_any_time\">隨時</string>\n    <string name=\"which_days\">%d天</string>\n    <string name=\"last_update_popup_check_time\">最近更新視窗跳出時刻：\\n%s</string>\n    <string name=\"no_update_popup_yet\">最近還沒跳出過更新視窗哦</string>\n    <string name=\"no_description\">您似乎還沒有寫介紹…</string>\n    <string name=\"share_to_others_tip\">複製以下內容，分享給其他人，可以透過頂部右側添加按鈕來將其存入設備：\\n%s</string>\n    <string name=\"delete_playlist\">刪除播放清單</string>\n    <string name=\"delete_the_playlist\">刪除該播放清單</string>\n    <string name=\"s_copy_to_clipboard\">「%1$s」已複製到剪貼簿</string>\n    <string name=\"detect_ha1_related_link_in_clipboard\">檢測到剪貼簿裡存在Hanime1相關連結</string>\n    <string name=\"enter\">進入</string>\n    <string name=\"sure_to_logout\">你確定要登出嗎？</string>\n    <string name=\"sure\">是的</string>\n    <string name=\"no\">不要</string>\n    <string name=\"not_logged_in\">未登入</string>\n    <string name=\"modify_success\">修改成功</string>\n    <string name=\"modify_title_or_desc\">修改標題或介紹</string>\n    <string name=\"fail_to_get_video_link\">無法取得該影片的播放連結，將會跳轉瀏覽器</string>\n    <string name=\"delete_success\">刪除成功</string>\n    <string name=\"video_might_not_exist\">該影片可能不存在</string>\n    <string name=\"video_not_exist\">該影片不存在</string>\n    <string name=\"add_to_h_keyframe\">加入關鍵H幀</string>\n    <string name=\"sure_to_add_to_h_keyframe\">確定要將當前時刻加入關鍵H幀嗎？</string>\n    <string name=\"current_position_d_ms\">當前時刻：%dms</string>\n    <string name=\"pause_then_long_press\">先暫停再長按</string>\n    <string name=\"long_press_to_add_h_keyframe\">請長按🥵添加關鍵H幀</string>\n    <string name=\"video_deleted_sure_to_delete_item\">影片已被刪除或移動，是否刪除該條目？</string>\n    <string name=\"not_allow_to_change\">不允許更改</string>\n    <string name=\"detailed_path_s\">詳細位置：%1$s</string>\n    <string name=\"long_press_pref_to_copy\">長按選項可以複製！</string>\n    <string name=\"watching_this_video_now\">當前正在觀看該影片哦~</string>\n    <string name=\"delete_failed\">刪除失敗</string>\n    <string name=\"delete_fav\">刪除喜歡</string>\n    <string name=\"attention\">注意！</string>\n    <string name=\"long_press_to_cancel_fav\">長按可以取消喜歡！</string>\n    <string name=\"ok\">好的</string>\n    <string name=\"modify_failed\">修改失敗</string>\n    <string name=\"add_failed\">添加失敗</string>\n    <string name=\"add_success\">添加成功</string>\n    <string name=\"default_\">預設</string>\n    <string name=\"alternative\">備用</string>\n    <string name=\"loading\">載入中…</string>\n    <string name=\"slide_to_choose_list\">請從右向左滑動選擇列表</string>\n    <string name=\"create_new_playlist\">創建新清單</string>\n    <string name=\"delete_watch_later\">刪除待看</string>\n    <string name=\"long_press_to_cancel_watch_later\">長按可以取消待看！</string>\n    <string name=\"delete_history\">刪除歷史記錄</string>\n    <string name=\"sure_to_delete_all_histories\">是否將影片觀看歷史記錄全部刪除🤔</string>\n    <string name=\"long_press_to_delete_all_histories\">長按可以刪除歷史記錄哦，右上角的刪除按鈕是負責刪除全部歷史記錄的！</string>\n    <string name=\"speed\">倍速</string>\n    <string name=\"cancel_thumb_up_success\">取消點讚成功！</string>\n    <string name=\"thumb_up_success\">點讚成功！</string>\n    <string name=\"cancel_thumb_down_success\">取消倒讚成功！</string>\n    <string name=\"thumb_down_success\">倒讚成功！</string>\n    <string name=\"interval_must_greater_than_d\">間隔太短，必須大於%ds</string>\n    <string name=\"load_reply_failed\">載入回覆失敗捏</string>\n    <string name=\"send_failed\">發送失敗！</string>\n    <string name=\"sending_reply\">發表回覆中</string>\n    <string name=\"send_success\">發送成功！</string>\n    <string name=\"sending_comment\">發表評論中</string>\n    <string name=\"there_is_a_small_issue\">出了點小問題…</string>\n    <string name=\"fav_failed\">喜歡失敗</string>\n    <string name=\"cancel_fav\">取消喜歡</string>\n    <string name=\"downloading_now\">正在下載中…</string>\n    <string name=\"already_in_queue\">在佇列中</string>\n    <string name=\"s_view_times\">%1$s次</string>\n    <string name=\"liked\">已喜歡</string>\n    <string name=\"cannot_download_here\">暫時無法從這裡下載！</string>\n    <string name=\"long_press_share_to_copy\">長按分享可以複製到剪貼簿中</string>\n    <string name=\"sure_to_download\">確定要下載嗎？</string>\n    <string name=\"go_to_official\">轉到官網</string>\n    <string name=\"download_video_detail_below\">將要下載的影片詳情如下</string>\n    <string name=\"name_with_colon\">名稱：</string>\n    <string name=\"quality_with_colon\">畫質：</string>\n    <string name=\"after_download_tips\">下載完畢後可以在「下載」介面找到下載後的影片，「設定」介面裡有詳細儲存路徑</string>\n    <string name=\"traditional_chinese\">正體中文</string>\n    <string name=\"simplified_chinese\">簡體中文</string>\n    <string name=\"restart_or_not_working\">修改%s需要重啟程式，否則不起作用！</string>\n    <string name=\"sure_to_clear\">確定要清除嗎？</string>\n    <string name=\"sure_to_clear_cache\">確定要清除快取嗎？</string>\n    <string name=\"clear_success\">清除成功</string>\n    <string name=\"clear_failed\">清除發生意外</string>\n    <string name=\"cache_empty\">當前快取為空，無需清理哦</string>\n    <string name=\"do_not_check_update_again\">別再檢查更新了！</string>\n    <string name=\"take_me_to_download\">帶我去下載</string>\n    <string name=\"d_speed_times\">%.1f倍</string>\n    <string name=\"high\">高</string>\n    <string name=\"moderately_high\">較高</string>\n    <string name=\"moderate\">適中</string>\n    <string name=\"slightly_low\">稍低</string>\n    <string name=\"low\">低</string>\n    <string name=\"very_low\">很低</string>\n    <string name=\"extremely_low\">極低</string>\n    <string name=\"update_failed_tips\">如果你發現軟體有重大問題但是提示更新失敗，請直接去 Github Releases 介面查看是否有最新版下載\\n\\n\n還有我竟然發現有人花錢買這 APP，真沒必要！</string>\n    <string name=\"shared_h_keyframe_detected_msg\">注意：如果你也有和對方相同代號的關鍵H幀，那麼你自己的將會被覆蓋\\n\n\\n\n標題：%1$s\\n\n代號：%2$s\\n\n有 %3$d 個時刻</string>\n    <string name=\"domain_change_tips\">修改域名需要重啟程式，否則不起作用！\\n\n不同域名不共通 Cookie，所以更換域名後需要重新進行登入操作。\\n\n所以除 hanime1.me 以外的域名可能在某些時間段會重定向回 hanime1.me，這可能導致 Cookie 失效\\n\n\\n\n建議如下：\\n\n· 無VPN用戶，使用 hanime1.me 並開啟內建 Hosts\\n\n· 日本用戶，使用 hanime1.com\\n\n· 其他地區用戶，使用 hanime1.me 並關閉內建 Hosts</string>\n    <string name=\"do_not_use_japan_ip\">不要使用日本IP位址!!!</string>\n    <string name=\"website_blocked_msg\">看到這裡說明他們網站被加固了，能不能恢復只能聽天命了…</string>\n    <string name=\"not_logged_in_currently\">當前未登入</string>\n    <string name=\"parse_error_msg\">可能這個網址解析起來不大一樣…</string>\n    <string name=\"network_unstable_msg\">可能是你的網路不穩定，多刷新！</string>\n    <string name=\"download_s_failed_many_times\">下載失敗多次：%1$s！</string>\n    <string name=\"download_failed_d_times_and_retry_s\">下載失敗 %1$d 次，正在重試：%2$s</string>\n    <string name=\"unknown_download_error\">未知下載錯誤</string>\n    <string name=\"download_task_completed\">下載任務已完成！</string>\n    <string name=\"download_completed_s\">下載完畢：%1$s</string>\n    <string name=\"this_data_exists\">該檔案已存在！</string>\n    <string name=\"download_failed_s_exists\">下載失敗：%1$s 已存在</string>\n    <string name=\"download_task_failed\">下載任務失敗！</string>\n    <string name=\"download_task_failed_s\">下載任務失敗：%1$s</string>\n    <string name=\"download_task_failed_s_reason_s\">下載失敗：%1$s\\n原因為：%2$s</string>\n    <string name=\"custom\">自訂</string>\n    <string name=\"show_bottom_progress\">顯示底部進度條</string>\n    <string name=\"default_playback_speed\">預設播放速度</string>\n    <string name=\"long_press_speed_multiplier\">長按快進速度是當前速度的</string>\n    <string name=\"slide_sensitivity\">滑動調整進度的靈敏度</string>\n    <string name=\"web_link\">網頁連結</string>\n    <string name=\"apply_deep_links\">預設開啟</string>\n    <string name=\"apply_deep_links_summary\">允許在這個應用程式中開啟網頁連結</string>\n    <string name=\"apply_deep_links_tips\">MIUI 隱藏了這個介面，所以你需要從這裡打開然後添加連結</string>\n    <string name=\"switch_player_kernel\">切換播放器內核</string>\n    <string name=\"switch_to_year\">切換至年</string>\n    <string name=\"switch_to_year_month\">切換至年月</string>\n    <string name=\"subscribe_failed\">訂閱失敗</string>\n    <string name=\"subscribe_success\">訂閱成功</string>\n    <string name=\"unsubscribe_success\">取消訂閱成功</string>\n    <string name=\"subscribe\">訂閱</string>\n    <string name=\"subscribed\">已訂閱</string>\n    <string name=\"unsubscribe\">取消訂閱</string>\n    <string name=\"unsubscribe_artist\">取消訂閱該作者</string>\n    <string name=\"sure_to_unsubscribe\">確定要取消訂閱嗎？</string>\n    <string name=\"action_app_open_by_default_settings_not_support\">此設備不支援從程式中開啟預設介面</string>\n    <string name=\"sure_to_redownload\">確定要重新下載嗎？</string>\n    <string name=\"subscription\">訂閱</string>\n    <string name=\"login_expired_auto_logout\">登入已過期，已自動登出</string>\n    <string name=\"crashlytics_summary\">開啟後，將會自動上報崩潰日誌，無須手動提交</string>\n    <string name=\"analytics_summary\">長按了解開啟對開發者的重要性</string>\n    <string name=\"crashlytics_title\">崩潰日誌自動上報</string>\n    <string name=\"analytics_title\">使用情況統計</string>\n    <string name=\"privacy\">隱私</string>\n    <string name=\"about_analytics_summary\">您同意讓開發者收集您的裝置資訊與使用方式嗎？這對開發有很大幫助。&lt;br&gt;&lt;br&gt;資料統計服務由 Google Analytics for Firebase 提供，其隱私政策可在 &lt;a href=&quot;https://www.google.com/policies/privacy/&quot;&gt;https://www.google.com/policies/privacy/&lt;/a&gt; 檢視。&lt;br&gt;&lt;br&gt;若您同意參與資料統計，您的裝置資訊與應用使用情況將會被記錄，其包括但不限於：Android API 版本、裝置型號、語言所在地、本應用版本號、會話時長、應用功能使用次數。開發者承諾以下資訊並不會被記錄：電話號碼、電子郵件地址、IMEI。&lt;br&gt;&lt;br&gt;在您同意參與資料統計之前，您的資訊不會被記錄。&lt;br&gt;&lt;br&gt;被記錄的資料會由 Google Analytics for Firebase 進行分析，分析報表僅由此應用程式開發者存取。</string>\n    <string name=\"about_analytics\">關於使用情況統計</string>\n    <string name=\"refresh_page_or_login_expired\">刷新一下頁面試試\\n也可能登入已過期</string>\n    <string name=\"website_blocked_msg_2\">網站被加固，修改為另外一個域名可能有奇效！</string>\n    <string name=\"website_blocked_msg_3\">可以提醒一下開發者，但不要催，網站被加固跟他沒有任何關係哦～</string>\n    <string name=\"action_not_support\">此設備不支援此操作</string>\n    <string name=\"category\">分類</string>\n    <string name=\"get_file_info_failed\">獲取檔案資訊失敗</string>\n    <string name=\"download_settings\">下載設定</string>\n    <string name=\"download_count_limit\">同時下載數量限制</string>\n    <string name=\"download_speed_limit\">下載速度限制</string>\n    <string name=\"no_limit\">無限制</string>\n    <string name=\"check_video_exists_in_download\">我注意到你好像下載過畫質為 %s 的該影片</string>\n    <string name=\"retry\">重試</string>\n    <string name=\"super_resolution\">超分辨率</string>\n    <string name=\"allow_pip\">開啓畫中畫模式</string>\n    <string name=\"allow_pip_summary\">返回主界面后小窗顯示視頻</string>\n    <string name=\"only_year\">隻選擇年</string>\n    <string name=\"year\">年</string>\n    <string name=\"month\">月</string>\n    <string name=\"day\">日</string>\n    <string name=\"ping_test\">正在測速，請耐心等待！</string>\n    <string name=\"use_ping_test\">是否測速</string>\n    <string name=\"use_ping_test_summary\">是否在應用啟動時對內置HOST進行測速，選擇速度最快的。</string>\n    <string name=\"external_playback\">外部觀看</string>\n    <string name=\"use_private_directory\">使用私有目錄</string>\n    <string name=\"allow_external_storage\">允許外部存儲權限</string>\n    <string name=\"reason_for_allow_external_storage\">不用擔心，這只是為了正常下載視頻，拒絕權限可能會下載失敗 \\n如果不放心可以在下載設置裏打開私有目錄</string>\n\n</resources>\n"
  },
  {
    "path": "app/src/main/res/values/themes.xml",
    "content": "<resources xmlns:tools=\"http://schemas.android.com/tools\">\n    <!-- Base application theme. -->\n    <style name=\"Theme.Hanime1\" parent=\"Theme.Material3.Dark.NoActionBar\">\n        <!-- Status bar color. -->\n        <item name=\"android:statusBarColor\">?android:colorBackground</item>\n        <item name=\"android:navigationBarColor\">?android:colorBackground</item>\n        <item name=\"android:windowTranslucentStatus\">false</item>\n        <!-- Customize your theme here. -->\n    </style>\n\n    <style name=\"Theme.Hanime1.Main\" parent=\"Theme.Hanime1\">\n        <item name=\"android:windowTranslucentStatus\">true</item>\n    </style>\n\n    <style name=\"Theme.Hanime1.Preview\" parent=\"Theme.Hanime1\">\n        <item name=\"android:windowTranslucentStatus\">true</item>\n    </style>\n\n    <style name=\"Theme.Hanime1.About\" parent=\"Theme.AppCompat.DayNight.NoActionBar\">\n        <item name=\"android:forceDarkAllowed\" tools:targetApi=\"q\">true</item>\n        <item name=\"android:windowTranslucentNavigation\">true</item>\n        <item name=\"android:windowTranslucentStatus\">true</item>\n    </style>\n\n    <style name=\"RoundCornerImageView\">\n        <item name=\"cornerFamily\">rounded</item>\n        <item name=\"cornerSize\">8dp</item>\n    </style>\n</resources>"
  },
  {
    "path": "app/src/main/res/values-en/strings.xml",
    "content": "<?xml version=\"1.0\" encoding=\"utf-8\"?>\n<resources>\n    <string name=\"home_page\">Home Page</string>\n    <string name=\"h_anime\">H Anime</string>\n    <string name=\"latest_hanime\">Latest H Anime</string>\n    <string name=\"fresh\">Fresh</string>\n    <string name=\"latest_upload\">Latest Upload</string>\n    <string name=\"this_month\">Monthly</string>\n    <string name=\"hot_video\">Hot Videos</string>\n    <string name=\"current\">Current</string>\n    <string name=\"hot_video_2\">Popular Anime</string>\n    <string name=\"trends\">Trends</string>\n    <string name=\"they_watched\">What They\\'re Watching</string>\n    <string name=\"search\">Search</string>\n    <string name=\"advanced_search\">Advanced Search</string>\n    <string name=\"back\">Back</string>\n    <string name=\"input_keyword\">Enter Keywords</string>\n    <string name=\"type\">Type</string>\n    <string name=\"tag\">Tag</string>\n    <string name=\"sort_option\">Sort Option</string>\n    <string name=\"brand\">Brand</string>\n    <string name=\"release_date\">Release Date</string>\n    <string name=\"duration\">Duration</string>\n    <string name=\"duration_able_reset\">Duration (Select to Reset)</string>\n    <string name=\"type_able_reset\">Type (Select to Reset)</string>\n    <string name=\"sort_option_able_reset\">Sort Option (Select to Reset)</string>\n    <string name=\"cancel_all_tags\">Cancel All Tags</string>\n    <string name=\"alert\">Alert</string>\n    <string name=\"alert_cancel_all_tags\">Are you sure you want to cancel all selected tags?</string>\n    <string name=\"confirm\">Confirm</string>\n    <string name=\"cancel\">Cancel</string>\n    <string name=\"content_tag\">Content Tags</string>\n    <string name=\"pair_widely\">Pair Widely</string>\n    <string name=\"pair_widely_alert\">More Results, Less Precision. Pair anime with any selected tag rather than all.</string>\n    <string name=\"reset\">Reset</string>\n    <string name=\"save\">Save</string>\n    <string name=\"save_and_search\">Save and Search</string>\n    <string name=\"video_attr\">Video Attributes</string>\n    <string name=\"relationship\">Character Relationships</string>\n    <string name=\"characteristics\">Characteristics</string>\n    <string name=\"appearance_and_figure\">Appearance and Figure</string>\n    <string name=\"story_plot\">Story Plot</string>\n    <string name=\"sex_position\">Sex Positions</string>\n    <string name=\"reset_date\">Reset Date</string>\n    <string name=\"copy_to_clipboard\">Copied to Clipboard</string>\n    <string name=\"introduction\">Introduction</string>\n    <string name=\"comment\">Comments</string>\n    <string name=\"collapse\">Collapse</string>\n    <string name=\"expand\">Expand</string>\n    <string name=\"series_video\">Series Videos</string>\n    <string name=\"related_video\">Related Videos</string>\n    <string name=\"now_playing\">Now Playing</string>\n    <string name=\"view_more_replies\">View More Replies</string>\n    <string name=\"comment_not_found\">Comments Not Found</string>\n    <string name=\"hanime_list\">New Anime List</string>\n    <string name=\"latest_hanime_tour\">New Anime Tour</string>\n    <string name=\"latest_hanime_list_monthly\">%s New Anime List</string>\n    <string name=\"latest_hanime_news\">New Anime News</string>\n    <string name=\"latest_hanime_comment\">%s Anime Comments</string>\n    <string name=\"my_list\">My List</string>\n    <string name=\"watch_later\">Watch Later</string>\n    <string name=\"fav_video\">Favorite Videos</string>\n    <string name=\"settings\">Settings</string>\n    <string name=\"video\">Video</string>\n    <string name=\"download\">Download</string>\n    <string name=\"watch_history\">Watch History</string>\n    <string name=\"login\">Login</string>\n    <string name=\"delete\">Delete</string>\n    <string name=\"here_is_empty\">It\\'s Empty Here…</string>\n    <string name=\"play_list\">Playlist</string>\n    <string name=\"add_to_fav\">Add to Favorites</string>\n    <string name=\"share\">Share</string>\n    <string name=\"add_to_watch_later\">Add to Watch Later</string>\n    <string name=\"video_language\">Video Language</string>\n    <string name=\"preference\">Preferences</string>\n    <string name=\"other\">Other</string>\n    <string name=\"about\">About</string>\n    <string name=\"check_update\">Check Update</string>\n    <string name=\"new_version_found\">New Version Found</string>\n    <string name=\"checking_update\">Checking for Updates…</string>\n    <string name=\"update_content\">Update Content</string>\n    <string name=\"check_update_failed\">Check for Update Failed. Click to Retry.</string>\n    <string name=\"check_update_success\">New Version Available: %s. Click to View</string>\n    <string name=\"current_version\">Current Version: %s</string>\n    <string name=\"already_latest_update\">Already the Latest Version!</string>\n    <string name=\"app_slogan\">Unofficial Hanime1.me Client</string>\n    <string name=\"search_placeholder\">What to watch today?</string>\n    <string name=\"downloading\">Downloading</string>\n    <string name=\"downloading_s\">Downloading: %1$s</string>\n    <string name=\"downloading_update_percent\">Downloading Update %d%%</string>\n    <string name=\"downloaded\">Downloaded</string>\n    <string name=\"start_download\">Start Download</string>\n    <string name=\"cancel_download\">Cancel Download</string>\n    <string name=\"local_playback\">Local Playback</string>\n    <string name=\"download_path\">Download Path</string>\n    <string name=\"help\">Help</string>\n    <string name=\"reply\">Reply</string>\n    <string name=\"reply_child_comment\">Reply to Child Comment</string>\n    <string name=\"comment_video\">Comment on this video</string>\n    <string name=\"login_first\">Please log in first</string>\n    <string name=\"reply_warning\">(Text will disappear when input bar disappears)</string>\n    <string name=\"clear_cache\">Clear Cache</string>\n    <string name=\"cache_occupy\">Cache Occupy</string>\n    <string name=\"cache_usage_summary\"><![CDATA[Currently using <b>%1$s</b> of storage space]]></string>\n    <string name=\"allow\">Allow</string>\n    <string name=\"deny\">Deny</string>\n    <string name=\"no_video_links_found\">No video links found</string>\n    <string name=\"child_comment\">Child Comments</string>\n    <string name=\"search_options_tips\">Long press a unit to clear all tags of the selected unit</string>\n    <string name=\"pause\">Pause</string>\n    <string name=\"continues\">Continue</string>\n    <string name=\"jump_to_webpage\">Go to Webpage</string>\n    <string name=\"sure_to_delete_s\">Are you sure you want to delete %s?</string>\n    <string name=\"sure_to_delete\">Are you sure you want to delete?</string>\n    <string name=\"prepare_to_delete_s\">You are about to delete\\n%s</string>\n    <string name=\"playlist_title\">Title</string>\n    <string name=\"playlist_description\">Description</string>\n    <string name=\"open_playlist_drawer\">Open Playlist Drawer</string>\n    <string name=\"add_to_playlist\">Add to Playlist</string>\n    <string name=\"player_settings\">Player Settings</string>\n    <string name=\"current_slide_sensitivity\">Current Sensitivity: %s</string>\n    <string name=\"network\">Network</string>\n    <string name=\"safe_dns\">Safe DNS</string>\n    <string name=\"proxy\">Proxy</string>\n    <string name=\"host_or_ip\">Host or IP</string>\n    <string name=\"port\">Port</string>\n    <string name=\"direct\">Direct Connection</string>\n    <string name=\"system_proxy\">System Proxy</string>\n    <string name=\"host_or_ipv4\">Host or IPv4</string>\n    <string name=\"sort_by_date_ascending\">Sort by Date Ascending</string>\n    <string name=\"sort_by_date_descending\">Sort by Date Descending</string>\n    <string name=\"sort_by_alphabet_ascending\">Sort by Alphabet Ascending</string>\n    <string name=\"sort_by_alphabet_descending\">Sort by Alphabet Descending</string>\n    <string name=\"h_keyframe_settings\">H Keyframe Settings</string>\n    <string name=\"h_keyframes_enable\">Enable H Keyframes!</string>\n    <string name=\"h_keyframes_enable_tip\">Disabling this feature will not delete your history data</string>\n    <string name=\"h_keyframes_disable_tip\">When enabled, relevant components will be displayed at the top of the player</string>\n    <string name=\"h_keyframe_title_prefix\">Video Code: </string>\n    <string name=\"manage\">Manage</string>\n    <string name=\"h_keyframe_manage\">Manage H Keyframes</string>\n    <string name=\"h_keyframe\">H Keyframe</string>\n    <string name=\"when_countdown_remind\">When to Start Countdown Reminder</string>\n    <string name=\"show_prompt_when_countdown\">Show Prompt on Countdown</string>\n    <string name=\"will_remind_before_d_seconds\">Will remind %d seconds before countdown</string>\n    <string name=\"shared_h_keyframes_enable\">Use Shared H Keyframes</string>\n    <string name=\"shared_h_keyframe_manage\">Manage Shared H Keyframes</string>\n    <string name=\"shared_h_keyframe_manage_tip\">Welcome to submit your H Keyframes on GitHub. If you don\\'t know how to submit, there are tutorial documents in the discussion area for reference.</string>\n    <string name=\"shared_h_keyframes_enable_tip\">Use the H Keyframes shared by kind-hearted people uploaded to GitHub</string>\n    <string name=\"shared\">Shared</string>\n    <string name=\"shared_h_keyframes_use_first\">Priority to Use Shared H Keyframes</string>\n    <string name=\"shared_h_keyframes_use_first_tip\">When both shared and local sets have H Keyframes for a video, the shared set is prioritized</string>\n    <string name=\"forum\">Forum</string>\n    <string name=\"forum_summary\">Welcome to vent. Important information will also be posted here</string>\n    <string name=\"submit_bug\">Submit Bug</string>\n    <string name=\"submit_bug_summary\">If there\\'s a bug, come and submit an issue</string>\n    <string name=\"position_ms\">Position (ms)</string>\n    <string name=\"prompt\">Prompt</string>\n    <string name=\"modify_h_keyframe\">Modify H Keyframe</string>\n    <string name=\"edit\">Edit</string>\n    <string name=\"title\">Title</string>\n    <string name=\"video_code\">Video Code</string>\n    <string name=\"copy_\">Copy</string>\n    <string name=\"add\">Add</string>\n    <string name=\"h_keyframes_shared_by_other_detected\">Detected H Keyframes shared by others</string>\n    <string name=\"h_keyframes_shared_by_other_not_detected\">No H Keyframes shared by others detected</string>\n    <string name=\"share_to_others\">Share with Others</string>\n    <string name=\"latest_release\">Latest Release</string>\n    <string name=\"chinese_subtitle\">Chinese Subtitle</string>\n    <string name=\"ranking_today\">Today\\'s Ranking</string>\n    <string name=\"ranking_this_month\">Monthly Ranking</string>\n    <string name=\"long_press_speed_not_supported\">Long press fast forward is temporarily not supported on your device</string>\n    <string name=\"start_all\">Start All</string>\n    <string name=\"pause_all\">Pause All</string>\n    <string name=\"domain_name\">Domain Name</string>\n    <string name=\"use_built_in_hosts\">Use Built-in Hosts</string>\n    <string name=\"network_settings\">Network Settings</string>\n    <string name=\"use_built_in_hosts_summary\">Mapping of hosts to IP provided by the app\\nNot in Chinese mainland, you don\\'t need to turn on this option</string>\n    <string name=\"username\">Username</string>\n    <string name=\"password\">Password</string>\n    <string name=\"try_login_here\">Try logging in here</string>\n    <string name=\"email\">Email</string>\n    <string name=\"login_failed\">Login Failed</string>\n    <string name=\"login_success\">Login Successful</string>\n    <string name=\"account_or_password_wrong\">Account or password may be incorrect</string>\n    <string name=\"login_tips\">This method can only keep you logged in for 2 hours… It\\'s still recommended to log in via the webpage,\\nbut the webpage doesn\\'t use Hosts, so you might need VPN</string>\n    <string name=\"ignore\">Ignore</string>\n    <string name=\"update\">Update</string>\n    <string name=\"use_ci_update_channel\">Use CI Update Channel</string>\n    <string name=\"use_ci_update_channel_summary\">CI Update channel can get the latest changes faster,\\nbut stability is not guaranteed</string>\n    <string name=\"update_popup_interval_days\">Update Check Popup Interval (Days)</string>\n    <string name=\"update_failed\">Update Failed</string>\n    <string name=\"update_download_background\">Download Updates in Background</string>\n    <string name=\"allow_install_from_unknown_app_sources\">Allow Installation from Unknown Sources</string>\n    <string name=\"reason_for_allow_install_from_unknown_app_sources\">Don\\'t worry, this is just for updating.\\nIf you\\'re not sure, you can download directly from GitHub</string>\n    <string name=\"go_to_settings\">Go to Settings</string>\n    <string name=\"allow_post_notification\">Allow Post Notifications</string>\n    <string name=\"reason_for_download_notification\">Please agree to notification permissions to receive download information in time!</string>\n    <string name=\"msg_deny_download_notification\">No notifications will be sent during or after the process</string>\n    <string name=\"at_any_time\">At Any Time</string>\n    <string name=\"which_days\">%d day(s)</string>\n    <string name=\"last_update_popup_check_time\">Last Update Popup Check Time: \\n%s</string>\n    <string name=\"no_update_popup_yet\">Haven\\'t seen any update popups lately!</string>\n    <string name=\"no_description\">You seem to have not written a description…</string>\n    <string name=\"share_to_others_tip\">Copy the following content and share it with others. You can add it to your device by clicking the add button on the top right:\\n%s</string>\n    <string name=\"delete_playlist\">Delete Playlist</string>\n    <string name=\"delete_the_playlist\">Delete This Playlist</string>\n    <string name=\"s_copy_to_clipboard\">\"%1$s\" copied to clipboard</string>\n    <string name=\"detect_ha1_related_link_in_clipboard\">Detected Hanime1 related links in clipboard</string>\n    <string name=\"enter\">Enter</string>\n    <string name=\"sure_to_logout\">Are you sure you want to log out?</string>\n    <string name=\"sure\">Yes</string>\n    <string name=\"no\">No</string>\n    <string name=\"not_logged_in\">Not Logged In</string>\n    <string name=\"modify_success\">Modification Successful</string>\n    <string name=\"modify_title_or_desc\">Modify Title or Description</string>\n    <string name=\"fail_to_get_video_link\">Unable to get playback link for this video, will be redirected to browser</string>\n    <string name=\"delete_success\">Deletion Successful</string>\n    <string name=\"video_might_not_exist\">The video might not exist</string>\n    <string name=\"video_not_exist\">Video does not exist</string>\n    <string name=\"add_to_h_keyframe\">Add to H Keyframe</string>\n    <string name=\"sure_to_add_to_h_keyframe\">Are you sure you want to add the current time to the H Keyframe?</string>\n    <string name=\"current_position_d_ms\">Current Time: %dms</string>\n    <string name=\"pause_then_long_press\">Pause first, then long press</string>\n    <string name=\"long_press_to_add_h_keyframe\">Long press 🥵 to add H Keyframe</string>\n    <string name=\"video_deleted_sure_to_delete_item\">Video has been deleted or moved, delete this entry?</string>\n    <string name=\"not_allow_to_change\">Not allowed to change</string>\n    <string name=\"detailed_path_s\">Detailed Location: %1$s</string>\n    <string name=\"long_press_pref_to_copy\">Long press options to copy!</string>\n    <string name=\"watching_this_video_now\">You are currently watching this video~</string>\n    <string name=\"delete_failed\">Deletion Failed</string>\n    <string name=\"delete_fav\">Delete Favorite</string>\n    <string name=\"attention\">Attention!</string>\n    <string name=\"long_press_to_cancel_fav\">Long press to cancel favorite!</string>\n    <string name=\"ok\">Okay</string>\n    <string name=\"modify_failed\">Modification Failed</string>\n    <string name=\"add_failed\">Addition Failed</string>\n    <string name=\"add_success\">Addition Successful</string>\n    <string name=\"default_\">Default</string>\n    <string name=\"alternative\">Alternative</string>\n    <string name=\"loading\">Loading…</string>\n    <string name=\"slide_to_choose_list\">Slide right to choose list</string>\n    <string name=\"create_new_playlist\">Create New Playlist</string>\n    <string name=\"delete_watch_later\">Delete Watch Later</string>\n    <string name=\"long_press_to_cancel_watch_later\">Long press to cancel watch later!</string>\n    <string name=\"delete_history\">Delete History</string>\n    <string name=\"sure_to_delete_all_histories\">Are you sure you want to delete all video watch histories?</string>\n    <string name=\"long_press_to_delete_all_histories\">Long press to delete history, the delete button in the upper right corner is responsible for deleting all history!</string>\n    <string name=\"speed\">Speed</string>\n    <string name=\"cancel_thumb_up_success\">Thumb Up Cancelled Successfully!</string>\n    <string name=\"thumb_up_success\">Thumb Up Successful!</string>\n    <string name=\"cancel_thumb_down_success\">Thumb Down Cancelled Successfully!</string>\n    <string name=\"thumb_down_success\">Thumb Down Successful!</string>\n    <string name=\"interval_must_greater_than_d\">Interval too short, must be greater than %ds</string>\n    <string name=\"load_reply_failed\">Failed to load replies</string>\n    <string name=\"send_failed\">Send Failed!</string>\n    <string name=\"sending_reply\">Sending Reply</string>\n    <string name=\"send_success\">Send Successful!</string>\n    <string name=\"sending_comment\">Sending Comment</string>\n    <string name=\"there_is_a_small_issue\">There is a small issue…</string>\n    <string name=\"fav_failed\">Favorite Failed</string>\n    <string name=\"cancel_fav\">Cancel Favorite</string>\n    <string name=\"downloading_now\">Downloading now…</string>\n    <string name=\"already_in_queue\">In Queue</string>\n    <string name=\"s_view_times\">%1$s views</string>\n    <string name=\"liked\">Liked</string>\n    <string name=\"cannot_download_here\">Cannot download from here temporarily!</string>\n    <string name=\"long_press_share_to_copy\">Long press share to copy to clipboard</string>\n    <string name=\"sure_to_download\">Are you sure you want to download?</string>\n    <string name=\"go_to_official\">Go to Official</string>\n    <string name=\"download_video_detail_below\">Details of the video to be downloaded below</string>\n    <string name=\"name_with_colon\">Name:</string>\n    <string name=\"quality_with_colon\">Quality:</string>\n    <string name=\"after_download_tips\">After downloading, you can find the downloaded video in the \"Download\" interface. Detailed storage paths are available in the \"Settings\" interface.</string>\n    <string name=\"traditional_chinese\">Traditional Chinese</string>\n    <string name=\"simplified_chinese\">Simplified Chinese</string>\n    <string name=\"restart_or_not_working\">Modifying %s requires restarting the program, otherwise it will not take effect!</string>\n    <string name=\"sure_to_clear\">Are you sure you want to clear?</string>\n    <string name=\"sure_to_clear_cache\">Are you sure you want to clear the cache?</string>\n    <string name=\"clear_success\">Clear Successful</string>\n    <string name=\"clear_failed\">Clear Failed</string>\n    <string name=\"cache_empty\">The current cache is empty, no need to clean</string>\n    <string name=\"do_not_check_update_again\">Stop checking for updates!</string>\n    <string name=\"take_me_to_download\">Take me to download</string>\n    <string name=\"d_speed_times\">%.1f times</string>\n    <string name=\"high\">High</string>\n    <string name=\"moderately_high\">Moderately High</string>\n    <string name=\"moderate\">Moderate</string>\n    <string name=\"slightly_low\">Slightly Low</string>\n    <string name=\"low\">Low</string>\n    <string name=\"very_low\">Very Low</string>\n    <string name=\"extremely_low\">Extremely Low</string>\n    <string name=\"update_failed_tips\">If you find significant issues with the software but the update fails, please go directly to the Github Releases page to see if there is a latest version available\\n\\nAlso, I just found out that someone actually paid for this app, it\\'s really unnecessary!</string>\n    <string name=\"shared_h_keyframe_detected_msg\">Note: If you also have H Keyframes with the same code as the other party, yours will be overwritten\\n\\nTitle: %1$s\\nCode: %2$s\\n%3$d moment(s) available</string>\n    <string name=\"domain_change_tips\">Changing the domain name requires restarting the program, otherwise it will not take effect!\\n\nDifferent domains do not share the same cookies, so after changing the domain name, you need to log in again.\\n\nSo, except for hanime1.me, other domain names may redirect back to hanime1.me at certain times, which may cause cookies to expire\\n\n\\n\nRecommendations are as follows:\\n\n· Non-VPN users, use hanime1.me and enable built-in Hosts\\n\n· Japanese users, use hanime1.com\\n\n· Users from other regions, use hanime1.me and disable built-in Hosts</string>\n    <string name=\"do_not_use_japan_ip\">Do not use Japanese IP address!!!</string>\n    <string name=\"website_blocked_msg\">Seeing this message means their website has been reinforced. Whether it can be restored depends on fate…</string>\n    <string name=\"not_logged_in_currently\">Currently not logged in</string>\n    <string name=\"parse_error_msg\">Maybe this URL is parsed differently…</string>\n    <string name=\"network_unstable_msg\">Your network may be unstable, please refresh more!</string>\n    <string name=\"download_s_failed_many_times\">Download failed many times: %1$s!</string>\n    <string name=\"download_failed_d_times_and_retry_s\">Download failed %1$d times, retrying %2$s</string>\n    <string name=\"unknown_download_error\">Unknown download error</string>\n    <string name=\"download_task_completed\">Download task completed!</string>\n    <string name=\"download_completed_s\">Download completed: %1$s</string>\n    <string name=\"this_data_exists\">This file already exists!</string>\n    <string name=\"download_failed_s_exists\">Download failed: %1$s already exists</string>\n    <string name=\"download_task_failed\">Download task failed!</string>\n    <string name=\"download_task_failed_s\">Download task failed: %1$s</string>\n    <string name=\"download_task_failed_s_reason_s\">Download task failed: %1$s\\nReason: %2$s</string>\n    <string name=\"custom\">Custom</string>\n    <string name=\"show_bottom_progress\">Show Bottom Progress Bar</string>\n    <string name=\"default_playback_speed\">Default Playback Speed</string>\n    <string name=\"long_press_speed_multiplier\">Long Press Speed Multiplier</string>\n    <string name=\"slide_sensitivity\">Slide Sensitivity for Progress Adjustment</string>\n    <string name=\"web_link\">Web Link</string>\n    <string name=\"apply_deep_links\">Apply Deep Links</string>\n    <string name=\"apply_deep_links_summary\">After application, quickly jump to this app when opening relevant web pages</string>\n    <string name=\"apply_deep_links_tips\">MIUI has hidden this interface, so you need to open it from here and then add the link.</string>\n    <string name=\"switch_player_kernel\">Switch Player Kernel</string>\n    <string name=\"switch_to_year\">Switch to Y</string>\n    <string name=\"switch_to_year_month\">Switch to YM</string>\n    <string name=\"subscribe_failed\">Subscribed Failed</string>\n    <string name=\"subscribe_success\">Subscribed Successfully</string>\n    <string name=\"unsubscribe_success\">Unsubscribed Successfully</string>\n    <string name=\"subscribe\">Subscribe</string>\n    <string name=\"subscribed\">Subscribed</string>\n    <string name=\"unsubscribe\">Unsubscribe</string>\n    <string name=\"unsubscribe_artist\">Unsubscribe Artist</string>\n    <string name=\"sure_to_unsubscribe\">Are you sure you want to unsubscribe?</string>\n    <string name=\"action_app_open_by_default_settings_not_support\">This device does not support opening deep link settings from the app</string>\n    <string name=\"sure_to_redownload\">Are you sure you want to redownload?</string>\n    <string name=\"subscription\">Subscription</string>\n    <string name=\"login_expired_auto_logout\">Login expired, automatically logged out</string>\n    <string name=\"crashlytics_summary\">After enabling, crash logs will be automatically reported without manual submission</string>\n    <string name=\"analytics_summary\">Long press to understand the importance of enabling this for developers</string>\n    <string name=\"crashlytics_title\">Automatic Crash Logs Reporting</string>\n    <string name=\"analytics_title\">Collect Usage Data</string>\n    <string name=\"privacy\">Privacy</string>\n    <string name=\"about_analytics_summary\">Would you mind if the developer collected your device information and your usage? It will help the developer a lot.&lt;br&gt;&lt;br&gt;Data analysis service is provided by Google Analytics for Firebase. Its privacy policy can be found at &lt;a href=&quot;https://www.google.com/policies/privacy/&quot;&gt;https://www.google.com/policies/privacy/&lt;/a&gt;.&lt;br&gt;&lt;br&gt;If you agree to join data analysis, your device information and your usage will be collected, including but not limited to Android API version, equipment model, language residence, the application version, conversion time, function times. The developer promise that these information will NOT be collected, including phone number, e-mail address, IMEI.&lt;br&gt;&lt;br&gt;Your information will not be collected until you agree to join data analysis.&lt;br&gt;&lt;br&gt;The collected information will be analyzed by Google Analytics for Firebase. The analysis report can only be viewed by the developer of this application.</string>\n    <string name=\"about_analytics\">About Analytics</string>\n    <string name=\"refresh_page_or_login_expired\">Try refreshing the page\\nor your login may have expired</string>\n    <string name=\"website_blocked_msg_2\">The website is blocked, changing to another domain name might work!</string>\n    <string name=\"website_blocked_msg_3\">You can notify the developer, but don\\'t rush them, the website being blocked is not their fault~</string>\n    <string name=\"action_not_support\">This device does not support this action</string>\n    <string name=\"category\">Category</string>\n    <string name=\"get_file_info_failed\">Failed to get file information</string>\n    <string name=\"download_settings\">Download Settings</string>\n    <string name=\"download_count_limit\">Download Count Limit</string>\n    <string name=\"download_speed_limit\">Download Speed Limit</string>\n    <string name=\"no_limit\">No Limit</string>\n    <string name=\"check_video_exists_in_download\">I noticed you seem to have downloaded the video with resolution %s</string>\n    <string name=\"retry\">Retry</string>\n    <string name=\"super_resolution\">Super Resolution</string>\n    <string name=\"allow_pip\">Open PIP</string>\n    <string name=\"allow_pip_summary\">Returning to the home screen displays the video in picture-in-picture (PiP) mode</string>\n    <string name=\"only_year\">Select only years</string>\n    <string name=\"year\">year</string>\n    <string name=\"month\">month</string>\n    <string name=\"day\">day</string>\n    <string name=\"ping_test\">The speed is being measured, please wait patiently!</string>\n    <string name=\"use_ping_test\">Whether to measure speed</string>\n    <string name=\"use_ping_test_summary\">Whether to measure the speed of the built-in HOST when the application starts, and choose the fastest one.</string>\n    <string name=\"external_playback\">External Playback</string>\n    <string name=\"use_private_directory\">Private Directory</string>\n    <string name=\"allow_external_storage\">Allow external storage permissions</string>\n    <string name=\"reason_for_allow_external_storage\">Don\\'t worry, this is just for downloading videos normally. If you refuse permission, the download may fail. \\nIf you are not at ease, you can open the private directory in the download settings.</string>\n\n</resources>"
  },
  {
    "path": "app/src/main/res/values-night/themes.xml",
    "content": "<resources xmlns:tools=\"http://schemas.android.com/tools\">\n    <!-- Base application theme. -->\n    \n</resources>"
  },
  {
    "path": "app/src/main/res/values-zh-rCN/strings.xml",
    "content": "<?xml version=\"1.0\" encoding=\"utf-8\"?>\n<resources>\n\n    <string name=\"home_page\">主页</string>\n    <string name=\"h_anime\">H动漫</string>\n    <string name=\"latest_hanime\">最新里番</string>\n    <string name=\"fresh\">新鲜</string>\n    <string name=\"latest_upload\">最新上传</string>\n    <string name=\"this_month\">本月</string>\n    <string name=\"hot_video\">热门视频</string>\n    <string name=\"current\">当前</string>\n    <string name=\"hot_video_2\">热门影片</string>\n    <string name=\"trends\">动态</string>\n    <string name=\"they_watched\">他们在看</string>\n    <string name=\"search\">搜索</string>\n    <string name=\"advanced_search\">高级搜索</string>\n    <string name=\"back\">返回</string>\n    <string name=\"input_keyword\">请输入关键词</string>\n    <string name=\"type\">类型</string>\n    <string name=\"tag\">标签</string>\n    <string name=\"sort_option\">排序方式</string>\n    <string name=\"brand\">品牌</string>\n    <string name=\"release_date\">发布日期</string>\n    <string name=\"duration\">片长</string>\n    <string name=\"duration_able_reset\">片长（复选可取消）</string>\n    <string name=\"type_able_reset\">类型（复选可取消）</string>\n    <string name=\"sort_option_able_reset\">排序方式（复选可取消）</string>\n    <string name=\"cancel_all_tags\">取消所有标签</string>\n    <string name=\"alert\">提示</string>\n    <string name=\"alert_cancel_all_tags\">确认将会取消你所选的所有标签</string>\n    <string name=\"confirm\">确认</string>\n    <string name=\"cancel\">取消</string>\n    <string name=\"content_tag\">内容标签</string>\n    <string name=\"pair_widely\">广泛配对</string>\n    <string name=\"pair_widely_alert\">较多结果，较不精准。配对所有包含任何一个选择的标签的影片，而非全部标签</string>\n    <string name=\"reset\">重置</string>\n    <string name=\"save\">保存</string>\n    <string name=\"save_and_search\">保存并搜索</string>\n    <string name=\"video_attr\">影片属性</string>\n    <string name=\"relationship\">人物关系</string>\n    <string name=\"characteristics\">角色设定</string>\n    <string name=\"appearance_and_figure\">外貌身材</string>\n    <string name=\"story_plot\">故事剧情</string>\n    <string name=\"sex_position\">性交体位</string>\n    <string name=\"reset_date\">重置日期</string>\n    <string name=\"copy_to_clipboard\">已复制到剪贴板</string>\n    <string name=\"introduction\">简介</string>\n    <string name=\"comment\">评论</string>\n    <string name=\"collapse\">收起</string>\n    <string name=\"expand\">展开</string>\n    <string name=\"series_video\">系列影片</string>\n    <string name=\"related_video\">相关影片</string>\n    <string name=\"now_playing\">现正播放</string>\n    <string name=\"view_more_replies\">查看更多回复</string>\n    <string name=\"comment_not_found\">未发现评论</string>\n    <string name=\"hanime_list\">新番列表</string>\n    <string name=\"latest_hanime_tour\">新番导览</string>\n    <string name=\"latest_hanime_list_monthly\">%s 里番新番列表</string>\n    <string name=\"latest_hanime_news\">新番资讯</string>\n    <string name=\"latest_hanime_comment\">%s 新番评论区</string>\n    <string name=\"my_list\">我的清单</string>\n    <string name=\"watch_later\">稍后观看</string>\n    <string name=\"fav_video\">喜欢的影片</string>\n    <string name=\"settings\">设置</string>\n    <string name=\"video\">影片</string>\n    <string name=\"download\">下载</string>\n    <string name=\"watch_history\">观看历史</string>\n    <string name=\"login\">登录</string>\n    <string name=\"delete\">删除</string>\n    <string name=\"here_is_empty\">这里是空空的…</string>\n    <string name=\"play_list\">播放清单</string>\n    <string name=\"add_to_fav\">加入喜欢</string>\n    <string name=\"share\">分享</string>\n    <string name=\"add_to_watch_later\">加入待看</string>\n    <string name=\"video_language\">影片语言</string>\n    <string name=\"preference\">偏好</string>\n    <string name=\"other\">其他</string>\n    <string name=\"about\">关于</string>\n    <string name=\"check_update\">检查更新</string>\n    <string name=\"new_version_found\">已发现新版本</string>\n    <string name=\"checking_update\">检查更新中…</string>\n    <string name=\"update_content\">更新内容</string>\n    <string name=\"check_update_failed\">检查更新失败，点击重试</string>\n    <string name=\"check_update_success\">当前有新版本：%s，点击查看</string>\n    <string name=\"current_version\">当前版本：%s</string>\n    <string name=\"already_latest_update\">已经是最新版本！</string>\n    <string name=\"app_slogan\">非官方 Hanime1.me 客户端</string>\n    <string name=\"search_placeholder\">今天想看点什么呢</string>\n    <string name=\"downloading\">正在下载</string>\n    <string name=\"downloading_s\">正在下载：%1$s</string>\n    <string name=\"downloading_update_percent\">正在下载更新 %d%%</string>\n    <string name=\"downloaded\">已下载</string>\n    <string name=\"start_download\">开始下载</string>\n    <string name=\"cancel_download\">取消下载</string>\n    <string name=\"local_playback\">本地观看</string>\n    <string name=\"download_path\">下载路径</string>\n    <string name=\"help\">帮助</string>\n    <string name=\"reply\">回复</string>\n    <string name=\"reply_child_comment\">回复子评论</string>\n    <string name=\"comment_video\">评论本影片</string>\n    <string name=\"login_first\">请先登录</string>\n    <string name=\"reply_warning\">（输入栏消失后输入文字也会消失）</string>\n    <string name=\"clear_cache\">清除缓存</string>\n    <string name=\"cache_occupy\">缓存占用</string>\n    <string name=\"cache_usage_summary\"><![CDATA[当前占用了 <b>%1$s</b> 的存储空间]]></string>\n    <string name=\"allow\">允许</string>\n    <string name=\"deny\">拒绝</string>\n    <string name=\"no_video_links_found\">未发现影片链接</string>\n    <string name=\"child_comment\">子评论</string>\n    <string name=\"search_options_tips\">长按某单元可以清除当前你所选单元的所有tag</string>\n    <string name=\"pause\">暂停</string>\n    <string name=\"continues\">继续</string>\n    <string name=\"jump_to_webpage\">转到网页</string>\n    <string name=\"sure_to_delete_s\">确定要删除 %s 吗？</string>\n    <string name=\"sure_to_delete\">确定要删除吗？</string>\n    <string name=\"prepare_to_delete_s\">你现在正要准备删除\\n%s</string>\n    <string name=\"playlist_title\">标题</string>\n    <string name=\"playlist_description\">介绍</string>\n    <string name=\"open_playlist_drawer\">打开播放清单列表</string>\n    <string name=\"add_to_playlist\">加入清单</string>\n    <string name=\"player_settings\">播放器设置</string>\n    <string name=\"current_slide_sensitivity\">当前灵敏度：%s</string>\n    <string name=\"network\">网络</string>\n    <string name=\"safe_dns\">安全 DNS</string>\n    <string name=\"proxy\">代理</string>\n    <string name=\"host_or_ip\">Host 或 IP</string>\n    <string name=\"port\">端口</string>\n    <string name=\"direct\">直接连接</string>\n    <string name=\"system_proxy\">系统代理</string>\n    <string name=\"host_or_ipv4\">Host 或 IPv4</string>\n    <string name=\"sort_by_date_ascending\">时间正排序</string>\n    <string name=\"sort_by_date_descending\">时间反排序</string>\n    <string name=\"sort_by_alphabet_ascending\">字母正排序</string>\n    <string name=\"sort_by_alphabet_descending\">字母反排序</string>\n    <string name=\"h_keyframe_settings\">关键H帧设置</string>\n    <string name=\"h_keyframes_enable\">关键H帧，启动！</string>\n    <string name=\"h_keyframes_enable_tip\">该功能关闭后并不会删除你的历史数据</string>\n    <string name=\"h_keyframes_disable_tip\">开启后，播放器顶部会有相关部件显示</string>\n    <string name=\"h_keyframe_title_prefix\">影片代码：</string>\n    <string name=\"manage\">管理</string>\n    <string name=\"h_keyframe_manage\">关键H帧管理</string>\n    <string name=\"h_keyframe\">关键H帧</string>\n    <string name=\"when_countdown_remind\">何时开始倒计时提醒</string>\n    <string name=\"show_prompt_when_countdown\">倒计时显示提示</string>\n    <string name=\"will_remind_before_d_seconds\">将会在 %d 秒前倒计时提醒</string>\n    <string name=\"shared_h_keyframes_enable\">采用共享关键H帧集</string>\n    <string name=\"shared_h_keyframe_manage\">共享关键H帧管理</string>\n    <string name=\"shared_h_keyframe_manage_tip\">欢迎到 GitHub 上面提交你的关键H帧，如果不会提交，讨论区有教学文档参考</string>\n    <string name=\"shared_h_keyframes_enable_tip\">采用好心人上传到 Github 的关键H帧集</string>\n    <string name=\"shared\">共享</string>\n    <string name=\"shared_h_keyframes_use_first\">优先采用共享关键H帧集</string>\n    <string name=\"shared_h_keyframes_use_first_tip\">当共享集和本地集都存在某一影片的关键H帧时，优先采用共享集</string>\n    <string name=\"forum\">讨论区</string>\n    <string name=\"forum_summary\">欢迎来吐槽，如果有重要信息也会发布到这里</string>\n    <string name=\"submit_bug\">提交 BUG</string>\n    <string name=\"submit_bug_summary\">出 BUG 了就来提交个 issue 吧</string>\n    <string name=\"position_ms\">位置 (ms)</string>\n    <string name=\"prompt\">提示</string>\n    <string name=\"modify_h_keyframe\">修改关键H帧</string>\n    <string name=\"edit\">修改</string>\n    <string name=\"title\">标题</string>\n    <string name=\"video_code\">影片代码</string>\n    <string name=\"copy_\">复制</string>\n    <string name=\"add\">添加</string>\n    <string name=\"h_keyframes_shared_by_other_detected\">检测到他人分享的关键H帧</string>\n    <string name=\"h_keyframes_shared_by_other_not_detected\">未检测到他人分享的关键H帧</string>\n    <string name=\"share_to_others\">分享给其他人</string>\n    <string name=\"latest_release\">最新上市</string>\n    <string name=\"chinese_subtitle\">中文字幕</string>\n    <string name=\"ranking_today\">本日排行</string>\n    <string name=\"ranking_this_month\">本月排行</string>\n    <string name=\"long_press_speed_not_supported\">您的设备暂时不支持长按快进</string>\n    <string name=\"start_all\">全部开始</string>\n    <string name=\"pause_all\">全部暂停</string>\n    <string name=\"domain_name\">域名</string>\n    <string name=\"use_built_in_hosts\">应用内置 Hosts</string>\n    <string name=\"network_settings\">网络设置</string>\n    <string name=\"use_built_in_hosts_summary\">应用提供的主机到 IP 的映射\\nVPN？那是什么？</string>\n    <string name=\"username\">账号</string>\n    <string name=\"password\">密码</string>\n    <string name=\"try_login_here\">试试在这里登录</string>\n    <string name=\"email\">电邮</string>\n    <string name=\"login_failed\">登录失败</string>\n    <string name=\"login_success\">登录成功</string>\n    <string name=\"account_or_password_wrong\">账户或密码可能错误</string>\n    <string name=\"login_tips\">该方法只能保持登录 2 个小时… 还是推荐使用网页登录，\\n但网页是不走 Hosts 的，所以可能需要 VPN</string>\n    <string name=\"ignore\">忽略</string>\n    <string name=\"update\">更新</string>\n    <string name=\"use_ci_update_channel\">采用 CI 更新频道</string>\n    <string name=\"use_ci_update_channel_summary\">CI 更新频道能更快地获取最新变更\\n但不保证稳定性</string>\n    <string name=\"update_popup_interval_days\">检查更新弹窗间隔天数</string>\n    <string name=\"update_failed\">更新失败</string>\n    <string name=\"update_download_background\">在后台下载更新</string>\n    <string name=\"allow_install_from_unknown_app_sources\">准许当前 App 来源安装</string>\n    <string name=\"reason_for_allow_install_from_unknown_app_sources\">别担心，这只是为了更新。\\n你如果不放心，可以直接去 Github 上下载</string>\n    <string name=\"go_to_settings\">前往设置</string>\n    <string name=\"allow_post_notification\">准许发送通知</string>\n    <string name=\"reason_for_download_notification\">请同意通知权限，及时收到下载信息！</string>\n    <string name=\"msg_deny_download_notification\">进行或完成时不会通知哦</string>\n    <string name=\"at_any_time\">随时</string>\n    <string name=\"which_days\">%d天</string>\n    <string name=\"last_update_popup_check_time\">最近更新弹窗跳出时刻：\\n%s</string>\n    <string name=\"no_update_popup_yet\">最近还没跳出过更新窗口哦</string>\n    <string name=\"no_description\">您似乎还没写介绍…</string>\n    <string name=\"share_to_others_tip\">复制以下内容，分享给其他人，可以通过顶部右侧添加按钮来将其存入设备：\\n%s</string>\n    <string name=\"delete_playlist\">删除播放清单</string>\n    <string name=\"delete_the_playlist\">删除该播放清单</string>\n    <string name=\"s_copy_to_clipboard\">「%1$s」已复制到剪贴板</string>\n    <string name=\"detect_ha1_related_link_in_clipboard\">检测到剪贴板里存在Hanime1相关链接</string>\n    <string name=\"enter\">进入</string>\n    <string name=\"sure_to_logout\">你确定要登出吗？</string>\n    <string name=\"sure\">是的</string>\n    <string name=\"no\">不要</string>\n    <string name=\"not_logged_in\">未登录</string>\n    <string name=\"modify_success\">修改成功</string>\n    <string name=\"modify_title_or_desc\">修改标题或介绍</string>\n    <string name=\"fail_to_get_video_link\">无法得到该影片的播放链接，即将转向浏览器</string>\n    <string name=\"delete_success\">删除成功</string>\n    <string name=\"video_might_not_exist\">可能该影片不存在</string>\n    <string name=\"video_not_exist\">该影片不存在</string>\n    <string name=\"add_to_h_keyframe\">加入关键H帧</string>\n    <string name=\"sure_to_add_to_h_keyframe\">确定要将当前时刻加入关键H帧吗？</string>\n    <string name=\"current_position_d_ms\">当前时刻：%dms</string>\n    <string name=\"pause_then_long_press\">先暂停再长按</string>\n    <string name=\"long_press_to_add_h_keyframe\">请长按🥵添加关键H帧</string>\n    <string name=\"video_deleted_sure_to_delete_item\">影片已被删除或移动，是否删除该条目？</string>\n    <string name=\"not_allow_to_change\">不允许更改</string>\n    <string name=\"detailed_path_s\">详细位置：%1$s</string>\n    <string name=\"long_press_pref_to_copy\">长按选项可以复制！</string>\n    <string name=\"watching_this_video_now\">当前正在观看该影片哦~</string>\n    <string name=\"delete_failed\">删除失败</string>\n    <string name=\"delete_fav\">删除喜欢</string>\n    <string name=\"attention\">注意！</string>\n    <string name=\"long_press_to_cancel_fav\">长按可以取消喜欢！</string>\n    <string name=\"ok\">好的</string>\n    <string name=\"modify_failed\">修改失败</string>\n    <string name=\"add_failed\">添加失败</string>\n    <string name=\"add_success\">添加成功</string>\n    <string name=\"default_\">默认</string>\n    <string name=\"alternative\">备用</string>\n    <string name=\"loading\">加载中…</string>\n    <string name=\"slide_to_choose_list\">请从右向左滑动选择列表</string>\n    <string name=\"create_new_playlist\">创建新清单</string>\n    <string name=\"delete_watch_later\">删除待看</string>\n    <string name=\"long_press_to_cancel_watch_later\">长按可以取消待看！</string>\n    <string name=\"delete_history\">删除历史记录</string>\n    <string name=\"sure_to_delete_all_histories\">是否将影片观看历史记录全部删除🤔</string>\n    <string name=\"long_press_to_delete_all_histories\">长按可以删除历史记录哦，右上角的删除按钮是负责删除全部历史记录的！</string>\n    <string name=\"speed\">倍速</string>\n    <string name=\"cancel_thumb_up_success\">取消点赞成功！</string>\n    <string name=\"thumb_up_success\">点赞成功！</string>\n    <string name=\"cancel_thumb_down_success\">取消点踩成功！</string>\n    <string name=\"thumb_down_success\">点踩成功！</string>\n    <string name=\"interval_must_greater_than_d\">间隔太短，必须大于%ds</string>\n    <string name=\"load_reply_failed\">加载回复失败了捏</string>\n    <string name=\"send_failed\">发送失败！</string>\n    <string name=\"sending_reply\">发表回复中</string>\n    <string name=\"send_success\">发送成功！</string>\n    <string name=\"sending_comment\">发表评论中</string>\n    <string name=\"there_is_a_small_issue\">出了点小问题…</string>\n    <string name=\"fav_failed\">喜欢失败</string>\n    <string name=\"cancel_fav\">取消喜欢</string>\n    <string name=\"downloading_now\">正在下载中…</string>\n    <string name=\"already_in_queue\">在队列中</string>\n    <string name=\"s_view_times\">%1$s次</string>\n    <string name=\"liked\">已喜欢</string>\n    <string name=\"cannot_download_here\">暂时无法从这里下载！</string>\n    <string name=\"long_press_share_to_copy\">长按分享可以复制到剪贴板中</string>\n    <string name=\"sure_to_download\">确定要下载吗？</string>\n    <string name=\"go_to_official\">转到官方</string>\n    <string name=\"download_video_detail_below\">将要下载的影片详情如下</string>\n    <string name=\"name_with_colon\">名称：</string>\n    <string name=\"quality_with_colon\">画质：</string>\n    <string name=\"after_download_tips\">下载完毕后可以在「下载」界面找到下载后的影片，「设置」界面里有详细存储路径</string>\n    <string name=\"traditional_chinese\">繁体中文</string>\n    <string name=\"simplified_chinese\">简体中文</string>\n    <string name=\"restart_or_not_working\">修改%s需要重启程序，否则不起作用！</string>\n    <string name=\"sure_to_clear\">确定要清除吗？</string>\n    <string name=\"sure_to_clear_cache\">确定要清除缓存吗？</string>\n    <string name=\"clear_success\">清除成功</string>\n    <string name=\"clear_failed\">清除发生意外</string>\n    <string name=\"cache_empty\">当前缓存为空，无需清理哦</string>\n    <string name=\"do_not_check_update_again\">别再检查更新了！</string>\n    <string name=\"take_me_to_download\">带我去下载</string>\n    <string name=\"d_speed_times\">%.1f倍</string>\n    <string name=\"high\">高</string>\n    <string name=\"moderately_high\">较高</string>\n    <string name=\"moderate\">适中</string>\n    <string name=\"slightly_low\">稍低</string>\n    <string name=\"low\">低</string>\n    <string name=\"very_low\">很低</string>\n    <string name=\"extremely_low\">极低</string>\n    <string name=\"update_failed_tips\">如果你发现软件有重大问题但是提示更新失败，请直接去 Github Releases 界面查看是否有最新版下载\\n\\n\n还有我竟然发现有人花钱买这 APP，真没必要哈！</string>\n    <string name=\"shared_h_keyframe_detected_msg\">注意：如果你也有和对方相同代号的关键H帧，那么你自己的将会被覆盖\\n\n\\n\n标题：%1$s\\n\n代号：%2$s\\n\n有 %3$d 个时刻</string>\n    <string name=\"domain_change_tips\">修改域名需要重启程序，否则不起作用！\\n\n不同域名不共通 Cookie，所以更换域名后需要重新进行登录操作。\\n\n所以除 hanime1.me 以外的域名可能在某些时间段会重定向回 hanime1.me，这可能导致 Cookie 失效\\n\n\\n\n建议如下：\\n\n· 无 VPN 用户，使用 hanime1.me 并开启内置 Hosts\\n\n· 日本用户，使用 hanime1.com\\n\n· 其他地区用户，使用 hanime1.me 并关闭内置 Hosts</string>\n    <string name=\"do_not_use_japan_ip\">不要使用日本IP地址!!!</string>\n    <string name=\"website_blocked_msg\">看到这里说明他们网站被加固了，能不能恢复只能听天命了…</string>\n    <string name=\"not_logged_in_currently\">当前未登录</string>\n    <string name=\"parse_error_msg\">可能这个网址解析起来不太一样…</string>\n    <string name=\"network_unstable_msg\">可能是你的网络不稳定，多刷新！</string>\n    <string name=\"download_s_failed_many_times\">下载失败多次：%1$s！</string>\n    <string name=\"download_failed_d_times_and_retry_s\">下载失败 %1$d 次，正在重试：%2$s</string>\n    <string name=\"unknown_download_error\">未知下载错误</string>\n    <string name=\"download_task_completed\">下载任务已完成！</string>\n    <string name=\"download_completed_s\">下载完毕：%1$s</string>\n    <string name=\"this_data_exists\">该文件已存在！</string>\n    <string name=\"download_failed_s_exists\">下载失败：%1$s 已存在</string>\n    <string name=\"download_task_failed\">下载任务失败！</string>\n    <string name=\"download_task_failed_s\">下载任务失败：%1$s</string>\n    <string name=\"download_task_failed_s_reason_s\">下载失败：%1$s\\n原因为：%2$s</string>\n    <string name=\"custom\">自定义</string>\n    <string name=\"show_bottom_progress\">显示底部进度条</string>\n    <string name=\"default_playback_speed\">默认播放速度</string>\n    <string name=\"long_press_speed_multiplier\">长按快进速度是当前速度的</string>\n    <string name=\"slide_sensitivity\">滑动调整进度的灵敏度</string>\n    <string name=\"web_link\">网页链接</string>\n    <string name=\"apply_deep_links\">应用深层链接</string>\n    <string name=\"apply_deep_links_summary\">应用后，打开相关网页后能快速跳转至本 App</string>\n    <string name=\"apply_deep_links_tips\">MIUI 隐藏了这个界面，所以你需要从这里打开然后添加链接</string>\n    <string name=\"switch_player_kernel\">切换播放器内核</string>\n    <string name=\"switch_to_year\">切换至年</string>\n    <string name=\"switch_to_year_month\">切换至年月</string>\n    <string name=\"subscribe_failed\">关注失败</string>\n    <string name=\"subscribe_success\">关注成功</string>\n    <string name=\"unsubscribe_success\">取消关注成功</string>\n    <string name=\"subscribe\">关注</string>\n    <string name=\"subscribed\">已关注</string>\n    <string name=\"unsubscribe\">取消关注</string>\n    <string name=\"unsubscribe_artist\">取消关注该作者</string>\n    <string name=\"sure_to_unsubscribe\">确定要取消关注吗？</string>\n    <string name=\"action_app_open_by_default_settings_not_support\">此设备不支持从程序中开启深层链接设置</string>\n    <string name=\"sure_to_redownload\">确定要重新下载吗？</string>\n    <string name=\"subscription\">关注</string>\n    <string name=\"login_expired_auto_logout\">登录已过期，已经自动登出</string>\n    <string name=\"crashlytics_summary\">开启后，将会自动上报崩溃日志，无须手动提交</string>\n    <string name=\"analytics_summary\">长按了解开启对开发者的重要性</string>\n    <string name=\"crashlytics_title\">崩溃日志自动上报</string>\n    <string name=\"analytics_title\">使用情况统计</string>\n    <string name=\"privacy\">隐私</string>\n    <string name=\"about_analytics_summary\">您同意让开发者收集您的设备信息与使用情况吗？这将对开发有很大帮助。&lt;br&gt;&lt;br&gt;数据统计服务由 Google Analytics for Firebase 提供，其隐私政策可在 &lt;a href=&quot;https://www.google.com/policies/privacy/&quot;&gt;https://www.google.com/policies/privacy/&lt;/a&gt; 查看。&lt;br&gt;&lt;br&gt;若您同意参与数据统计，您的设备信息与应用使用情况将会被记录，其包括但不限于：Android API 版本、设备型号、语言所在地、本应用版本号、会话时长、应用功能使用次数。开发者承诺以下信息并不会被记录：电话号码、电子邮件地址、IMEI。&lt;br&gt;&lt;br&gt;在您同意参与数据统计之前，您的信息不会被记录。&lt;br&gt;&lt;br&gt;被记录的数据会由 Google Analytics for Firebase 进行分析，分析报表仅可被本应用开发者访问。</string>\n    <string name=\"about_analytics\">关于使用情况统计</string>\n    <string name=\"refresh_page_or_login_expired\">刷新一下页面试试\\n也可能登入已过期</string>\n    <string name=\"website_blocked_msg_2\">网站被加固，修改为另外一个域名可能有奇效！</string>\n    <string name=\"website_blocked_msg_3\">可以提醒一下开发者，但不要催，网站被加固跟他没有任何关系哦～</string>\n    <string name=\"action_not_support\">此设备不支持该操作</string>\n    <string name=\"category\">分类</string>\n    <string name=\"get_file_info_failed\">获取文件信息失败</string>\n    <string name=\"download_settings\">下载设置</string>\n    <string name=\"download_count_limit\">同时下载数量限制</string>\n    <string name=\"download_speed_limit\">下载速度限制</string>\n    <string name=\"no_limit\">无限制</string>\n    <string name=\"check_video_exists_in_download\">我注意到你好像下载过分辨率为 %s 的该影片</string>\n    <string name=\"retry\">重试</string>\n    <string name=\"super_resolution\">超分辨率</string>\n    <string name=\"allow_pip\">开启画中画模式</string>\n    <string name=\"allow_pip_summary\">返回主界面后小窗显示视频</string>\n    <string name=\"only_year\">只选择年</string>\n    <string name=\"year\">年</string>\n    <string name=\"month\">月</string>\n    <string name=\"day\">日</string>\n    <string name=\"ping_test\">正在测速，请耐心等待！</string>\n    <string name=\"use_ping_test\">是否测速</string>\n    <string name=\"use_ping_test_summary\">是否在应用启动时对内置HOST进行测速，选择速度最快的。</string>\n    <string name=\"external_playback\">外部观看</string>\n    <string name=\"use_private_directory\">使用私有目录</string>\n    <string name=\"allow_external_storage\">允许外部存储权限</string>\n    <string name=\"reason_for_allow_external_storage\">不用担心，这只是为了正常下载视频，拒绝权限可能会下载失败 \\n如果不放心可以在下载设置里打开私有目录</string>\n\n</resources>"
  },
  {
    "path": "app/src/main/res/xml/backup_rules.xml",
    "content": "<?xml version=\"1.0\" encoding=\"utf-8\"?><!--\n   Sample backup rules file; uncomment and customize as necessary.\n   See https://developer.android.com/guide/topics/data/autobackup\n   for details.\n   Note: This file is ignored for devices older that API 31\n   See https://developer.android.com/about/versions/12/backup-restore\n-->\n<full-backup-content>\n    <!--\n   <include domain=\"sharedpref\" path=\".\"/>\n   <exclude domain=\"sharedpref\" path=\"device.xml\"/>\n-->\n</full-backup-content>"
  },
  {
    "path": "app/src/main/res/xml/data_extraction_rules.xml",
    "content": "<?xml version=\"1.0\" encoding=\"utf-8\"?><!--\n   Sample data extraction rules file; uncomment and customize as necessary.\n   See https://developer.android.com/about/versions/12/backup-restore#xml-changes\n   for details.\n-->\n<data-extraction-rules>\n    <cloud-backup>\n        <!-- TODO: Use <include> and <exclude> to control what is backed up.\n        <include .../>\n        <exclude .../>\n        -->\n    </cloud-backup>\n    <!--\n    <device-transfer>\n        <include .../>\n        <exclude .../>\n    </device-transfer>\n    -->\n</data-extraction-rules>"
  },
  {
    "path": "app/src/main/res/xml/file_paths.xml",
    "content": "<?xml version=\"1.0\" encoding=\"utf-8\"?>\n<paths>\n    <external-files-path\n        name=\"hanime_video\"\n        path=\"Movies\" />\n    <external-path\n        name=\"external\"\n        path=\".\" />\n    <external-files-path\n        name=\"external_files\"\n        path=\".\" />\n    <cache-path\n        name=\"cache\"\n        path=\".\" />\n    <external-cache-path\n        name=\"external_cache\"\n        path=\".\" />\n    <files-path\n        name=\"files\"\n        path=\".\" />\n</paths>"
  },
  {
    "path": "app/src/main/res/xml/locales_config.xml",
    "content": "<?xml version=\"1.0\" encoding=\"utf-8\"?>\n<locale-config xmlns:android=\"http://schemas.android.com/apk/res/android\">\n    <locale android:name=\"zh-Hant\" />\n    <locale android:name=\"zh-Hans\" />\n</locale-config>"
  },
  {
    "path": "app/src/main/res/xml/settings_download.xml",
    "content": "<?xml version=\"1.0\" encoding=\"utf-8\"?>\n<androidx.preference.PreferenceScreen xmlns:android=\"http://schemas.android.com/apk/res/android\"\n    xmlns:app=\"http://schemas.android.com/apk/res-auto\">\n\n    <com.yenaly.yenaly_libs.base.preference.LongClickablePreference\n        app:iconSpaceReserved=\"false\"\n        app:key=\"download_path\"\n        app:title=\"@string/download_path\" />\n\n    <SeekBarPreference\n        android:max=\"10\"\n        app:defaultValue=\"2\"\n        app:iconSpaceReserved=\"false\"\n        app:key=\"download_count_limit\"\n        app:min=\"0\"\n        app:title=\"@string/download_count_limit\"\n        app:updatesContinuously=\"true\" />\n\n    <SeekBarPreference\n        app:iconSpaceReserved=\"false\"\n        app:key=\"download_speed_limit\"\n        app:title=\"@string/download_speed_limit\"\n        app:updatesContinuously=\"true\" />\n\n    <com.yenaly.yenaly_libs.base.preference.MaterialSwitchPreference\n        app:iconSpaceReserved=\"false\"\n        app:key=\"use_private_directory\"\n        app:title=\"@string/use_private_directory\" />\n\n</androidx.preference.PreferenceScreen>"
  },
  {
    "path": "app/src/main/res/xml/settings_h_keyframe.xml",
    "content": "<?xml version=\"1.0\" encoding=\"utf-8\"?>\n<PreferenceScreen xmlns:android=\"http://schemas.android.com/apk/res/android\"\n    xmlns:app=\"http://schemas.android.com/apk/res-auto\">\n\n    <com.yenaly.yenaly_libs.base.preference.MaterialSwitchPreference\n        app:defaultValue=\"true\"\n        app:iconSpaceReserved=\"false\"\n        app:key=\"h_keyframes_enable\"\n        app:summary=\"@string/h_keyframes_disable_tip\"\n        app:title=\"@string/h_keyframes_enable\" />\n\n    <PreferenceCategory\n        app:iconSpaceReserved=\"false\"\n        app:key=\"h_keyframe_manage_category\"\n        app:title=\"@string/manage\">\n\n        <Preference\n            app:iconSpaceReserved=\"false\"\n            app:key=\"h_keyframe_manage\"\n            app:title=\"@string/h_keyframe_manage\" />\n\n\n    </PreferenceCategory>\n\n    <PreferenceCategory\n        app:iconSpaceReserved=\"false\"\n        app:key=\"shared_h_keyframes_category\"\n        app:title=\"@string/shared\">\n\n        <com.yenaly.yenaly_libs.base.preference.MaterialSwitchPreference\n            app:defaultValue=\"true\"\n            app:iconSpaceReserved=\"false\"\n            app:key=\"shared_h_keyframes_enable\"\n            app:summary=\"@string/shared_h_keyframes_enable_tip\"\n            app:title=\"@string/shared_h_keyframes_enable\" />\n\n        <com.yenaly.yenaly_libs.base.preference.MaterialSwitchPreference\n            app:defaultValue=\"false\"\n            app:iconSpaceReserved=\"false\"\n            app:key=\"shared_h_keyframes_use_first\"\n            app:summary=\"@string/shared_h_keyframes_use_first_tip\"\n            app:title=\"@string/shared_h_keyframes_use_first\" />\n\n        <Preference\n            app:iconSpaceReserved=\"false\"\n            app:key=\"shared_h_keyframe_manage\"\n            app:summary=\"@string/shared_h_keyframe_manage_tip\"\n            app:title=\"@string/shared_h_keyframe_manage\" />\n\n    </PreferenceCategory>\n\n    <PreferenceCategory\n        app:iconSpaceReserved=\"false\"\n        app:key=\"h_keyframe_custom_category\"\n        app:title=\"@string/custom\">\n\n        <com.yenaly.yenaly_libs.base.preference.MaterialSwitchPreference\n            app:defaultValue=\"false\"\n            app:iconSpaceReserved=\"false\"\n            app:key=\"show_comment_when_countdown\"\n            app:title=\"@string/show_prompt_when_countdown\" />\n\n        <SeekBarPreference\n            android:max=\"30\"\n            app:defaultValue=\"10\"\n            app:iconSpaceReserved=\"false\"\n            app:key=\"when_countdown_remind\"\n            app:min=\"5\"\n            app:title=\"@string/when_countdown_remind\"\n            app:updatesContinuously=\"true\" />\n\n    </PreferenceCategory>\n\n</PreferenceScreen>"
  },
  {
    "path": "app/src/main/res/xml/settings_home.xml",
    "content": "<?xml version=\"1.0\" encoding=\"utf-8\"?>\n<PreferenceScreen xmlns:android=\"http://schemas.android.com/apk/res/android\"\n    xmlns:app=\"http://schemas.android.com/apk/res-auto\">\n\n    <PreferenceCategory app:title=\"@string/video\">\n\n        <com.yenaly.han1meviewer.ui.view.pref.MaterialDialogPreference\n            app:icon=\"@drawable/baseline_simp_to_trad_24\"\n            app:key=\"video_language\"\n            app:title=\"@string/video_language\"\n            app:useSimpleSummaryProvider=\"true\" />\n\n        <Preference\n            app:icon=\"@drawable/ic_baseline_play_circle_outline_24\"\n            app:key=\"player_settings\"\n            app:title=\"@string/player_settings\" />\n\n        <com.yenaly.yenaly_libs.base.preference.MaterialSwitchPreference\n            app:icon=\"@drawable/pip_24px\"\n            app:defaultValue=\"false\"\n            app:key=\"allow_pip\"\n            app:summary=\"@string/allow_pip_summary\"\n            app:title=\"@string/allow_pip\" />\n\n        <!-- #issue-104: 关键H帧移出设置 -->\n\n        <Preference\n            app:icon=\"@drawable/baseline_h_24\"\n            app:key=\"h_keyframe_settings\"\n            app:title=\"@string/h_keyframe_settings\" />\n\n    </PreferenceCategory>\n\n    <PreferenceCategory app:title=\"@string/download\">\n\n        <Preference\n            app:icon=\"@drawable/ic_baseline_download_24\"\n            app:key=\"download_settings\"\n            app:title=\"@string/download_settings\" />\n\n    </PreferenceCategory>\n\n    <PreferenceCategory app:title=\"@string/network\">\n\n        <Preference\n            app:icon=\"@drawable/ic_baseline_language_24\"\n            app:key=\"network_settings\"\n            app:title=\"@string/network_settings\" />\n\n        <Preference\n            app:icon=\"@drawable/baseline_add_link_24\"\n            app:key=\"apply_deep_links\"\n            app:summary=\"@string/apply_deep_links_summary\"\n            app:title=\"@string/apply_deep_links\" />\n\n    </PreferenceCategory>\n\n    <PreferenceCategory app:title=\"@string/update\">\n\n        <Preference\n            app:icon=\"@drawable/ic_baseline_update_24\"\n            app:key=\"update\"\n            app:title=\"@string/check_update\" />\n\n        <com.yenaly.yenaly_libs.base.preference.MaterialSwitchPreference\n            app:defaultValue=\"false\"\n            app:key=\"use_ci_update_channel\"\n            app:summary=\"@string/use_ci_update_channel_summary\"\n            app:title=\"@string/use_ci_update_channel\" />\n\n        <SeekBarPreference\n            android:max=\"30\"\n            app:key=\"update_popup_interval_days\"\n            app:min=\"0\"\n            app:title=\"@string/update_popup_interval_days\"\n            app:updatesContinuously=\"true\" />\n\n\n    </PreferenceCategory>\n\n    <PreferenceCategory app:title=\"@string/privacy\">\n\n        <com.yenaly.han1meviewer.ui.view.pref.HPrivacyPreference\n            app:defaultValue=\"true\"\n            app:icon=\"@drawable/baseline_data_usage_24\"\n            app:key=\"use_analytics\"\n            app:summary=\"@string/analytics_summary\"\n            app:title=\"@string/analytics_title\" />\n\n    </PreferenceCategory>\n\n    <PreferenceCategory app:title=\"@string/other\">\n\n        <Preference\n            app:icon=\"@drawable/ic_baseline_clear_all_24\"\n            app:key=\"clear_cache\"\n            app:title=\"@string/clear_cache\" />\n\n        <Preference\n            app:icon=\"@drawable/baseline_bug_report_24\"\n            app:key=\"submit_bug\"\n            app:summary=\"@string/submit_bug_summary\"\n            app:title=\"@string/submit_bug\" />\n\n        <Preference\n            app:icon=\"@drawable/baseline_forum_24\"\n            app:key=\"forum\"\n            app:summary=\"@string/forum_summary\"\n            app:title=\"@string/forum\" />\n\n        <Preference\n            app:icon=\"@drawable/ic_baseline_info_24\"\n            app:key=\"about\"\n            app:summary=\"@string/hanime_app_name\"\n            app:title=\"@string/about\" />\n\n    </PreferenceCategory>\n\n</PreferenceScreen>"
  },
  {
    "path": "app/src/main/res/xml/settings_network.xml",
    "content": "<?xml version=\"1.0\" encoding=\"utf-8\"?>\n<PreferenceScreen xmlns:app=\"http://schemas.android.com/apk/res-auto\">\n    <com.yenaly.han1meviewer.ui.view.pref.MaterialDialogPreference\n        app:iconSpaceReserved=\"false\"\n        app:key=\"domain_name\"\n        app:title=\"@string/domain_name\"\n        app:useSimpleSummaryProvider=\"true\" />\n\n    <Preference\n        app:iconSpaceReserved=\"false\"\n        app:key=\"proxy\"\n        app:title=\"@string/proxy\" />\n\n    <com.yenaly.yenaly_libs.base.preference.MaterialSwitchPreference\n        app:iconSpaceReserved=\"false\"\n        app:key=\"use_built_in_hosts\"\n        app:summary=\"@string/use_built_in_hosts_summary\"\n        app:title=\"@string/use_built_in_hosts\" />\n\n    <com.yenaly.yenaly_libs.base.preference.MaterialSwitchPreference\n        app:iconSpaceReserved=\"false\"\n        app:key=\"ping_test\"\n        app:summary=\"@string/use_ping_test_summary\"\n        app:title=\"@string/use_ping_test\" />\n\n</PreferenceScreen>"
  },
  {
    "path": "app/src/main/res/xml/settings_player.xml",
    "content": "<?xml version=\"1.0\" encoding=\"utf-8\"?>\n<PreferenceScreen xmlns:android=\"http://schemas.android.com/apk/res/android\"\n    xmlns:app=\"http://schemas.android.com/apk/res-auto\">\n\n    <com.yenaly.han1meviewer.ui.view.pref.MaterialDialogPreference\n        app:iconSpaceReserved=\"false\"\n        app:key=\"switch_player_kernel\"\n        app:title=\"@string/switch_player_kernel\"\n        app:useSimpleSummaryProvider=\"true\" />\n\n    <com.yenaly.yenaly_libs.base.preference.MaterialSwitchPreference\n        app:defaultValue=\"true\"\n        app:iconSpaceReserved=\"false\"\n        app:key=\"show_bottom_progress\"\n        app:title=\"@string/show_bottom_progress\" />\n\n    <com.yenaly.han1meviewer.ui.view.pref.MaterialDialogPreference\n        app:iconSpaceReserved=\"false\"\n        app:key=\"player_speed\"\n        app:title=\"@string/default_playback_speed\"\n        app:useSimpleSummaryProvider=\"true\" />\n\n    <com.yenaly.han1meviewer.ui.view.pref.MaterialDialogPreference\n        app:iconSpaceReserved=\"false\"\n        app:key=\"long_press_speed_times\"\n        app:title=\"@string/long_press_speed_multiplier\"\n        app:useSimpleSummaryProvider=\"true\" />\n\n    <SeekBarPreference\n        android:max=\"9\"\n        app:defaultValue=\"5\"\n        app:iconSpaceReserved=\"false\"\n        app:key=\"slide_sensitivity\"\n        app:min=\"1\"\n        app:title=\"@string/slide_sensitivity\"\n        app:updatesContinuously=\"true\" />\n\n\n</PreferenceScreen>"
  },
  {
    "path": "app/src/test/java/com/yenaly/han1meviewer/ExampleUnitTest.kt",
    "content": "package com.yenaly.han1meviewer\n\nimport org.junit.Assert.assertEquals\nimport org.junit.Test\n\n/**\n * Example local unit test, which will execute on the development machine (host).\n *\n * See [testing documentation](http://d.android.com/tools/testing).\n */\nclass ExampleUnitTest {\n    @Test\n    fun addition_isCorrect() {\n        assertEquals(4, 2 + 2)\n    }\n}"
  },
  {
    "path": "build.gradle.kts",
    "content": "// Top-level build file where you can add configuration options common to all sub-projects/modules.\nplugins {\n    alias(libs.plugins.com.android.application) apply false\n    alias(libs.plugins.com.android.library) apply false\n    alias(libs.plugins.org.jetbrains.kotlin.android) apply false\n    alias(libs.plugins.org.jetbrains.kotlin.plugin.serialization) apply false\n    alias(libs.plugins.org.jetbrains.kotlin.plugin.parcelize) apply false\n    alias(libs.plugins.com.google.gms.google.services) apply false\n    alias(libs.plugins.com.google.firebase.crashlytics) apply false\n    alias(libs.plugins.com.google.firebase.firebase.pref) apply false\n    // alias(libs.plugins.compose.compiler) apply false\n}\n\ntask<Delete>(\"clean\") {\n    delete(rootProject.layout.buildDirectory)\n}"
  },
  {
    "path": "buildSrc/build.gradle.kts",
    "content": "plugins {\n    `kotlin-dsl`\n}\n\nrepositories {\n    mavenCentral()\n    google()\n    maven(\"https://jitpack.io\")\n}"
  },
  {
    "path": "buildSrc/src/main/java/Config.kt",
    "content": "@file:Suppress(\"UnstableApiUsage\")\n\nimport org.gradle.api.Project\nimport java.time.Clock\nimport java.time.LocalDateTime\nimport java.time.ZoneId\nimport java.time.format.DateTimeFormatter\n\n/**\n * @author Yenaly Liew\n * @time 2023/11/25 025 17:55\n */\nobject Config {\n\n    val Project.isRelease: Boolean\n        get() = gradle.startParameter.taskNames.any { it.contains(\"Release\") }\n\n    object Version {\n\n        const val DEBUG = \"debug\"\n        const val RELEASE = \"release\"\n        const val CI = \"ci\"\n\n        /**\n         * 创建版本号\n         *\n         * @return 版本号和版本名\n         *\n         *         Example:\n         *         ci: 0.1.2-ci+21032500\n         *         release: 0.1.2-release+21032500\n         *         debug (fixed): debug+1\n         */\n        fun Project.createVersion(\n            major: Int, minor: Int, patch: Int\n        ): Pair<Int, String> {\n            val source = this.source\n            val versionCode: Int\n            val versionName: String\n            when (source) {\n                DEBUG -> {\n                    versionCode = 1\n                    versionName = \"$DEBUG+$versionCode\"\n                }\n\n                else -> {\n                    versionCode = LocalDateTime.now(Clock.systemUTC()).format(\n                        DateTimeFormatter.ofPattern(\"yyMMddHH\")\n                    ).toInt()\n                    versionName = \"${major}.${minor}.${patch}-$source+$versionCode\"\n                }\n            }\n            println(\"Version Code: $versionCode\")\n            println(\"Version Name: $versionName\")\n            return versionCode to versionName\n        }\n\n        /**\n         * 版本来源，用于区分不同的版本\n         *\n         * @return ci 或 release 或 debug\n         */\n        val Project.source: String\n            get() = System.getenv(\"HA1_VERSION_SOURCE\") ?: kotlin.run {\n                return if (isRelease) RELEASE else DEBUG\n            }\n    }\n\n    val Project.lastCommitSha: String\n        get() = providers.exec {\n            commandLine = \"git rev-parse --short=7 HEAD\".split(' ')\n        }.standardOutput.asText.get().trim()\n\n    val thisYear: Int\n        get() = LocalDateTime.now(Clock.system(ZoneId.of(\"UTC+8\"))).year\n}"
  },
  {
    "path": "gradle/libs.versions.toml",
    "content": "[versions]\n# Plugins\nandroidGradle = \"8.9.0\"\nkotlin = \"2.0.21\"\nserializationPlugin = \"2.0.21\"\nksp = \"2.0.21-1.0.27\"\n\n# Dependencies\nabout = \"2.5.2\"\ncoreKtx = \"1.15.0\"\nappCompat = \"1.7.0\"\nmaterial = \"1.14.0-alpha02\"\ncoroutinesAndroid = \"1.9.0\"\nactivity = \"1.10.1\"\nfragment = \"1.8.6\"\nconstraintLayout = \"2.2.1\"\nrecyclerView = \"1.4.0\"\nexpandableTextView = \"v1.6.1-x\"\nbaseRecyclerViewAdapterHelper3 = \"3.0.14\"\nbaseRecyclerViewAdapterHelper4 = \"4.1.4\"\nrefreshLayoutKernel = \"2.0.5\"\nrefreshHeaderMaterial = \"2.0.5\"\nrefreshFooterClassics = \"2.0.5\"\nmultiType = \"4.3.0\"\nlifecycleViewModelKtx = \"2.8.7\"\nlifecycleRuntimeKtx = \"2.8.7\"\nlifecycleLiveDataKtx = \"2.8.7\"\nroomRuntime = \"2.6.1\"\nroomKtx = \"2.6.1\"\nroomCompiler = \"2.6.1\"\nnavigationFragmentKtx = \"2.8.8\"\nnavigationUiKtx = \"2.8.8\"\npreferenceKtx = \"1.2.1\"\nworkRuntime = \"2.10.0\"\nworkRuntimeKtx = \"2.10.0\"\nstartupRuntime = \"1.2.0\"\npalette = \"1.0.0\"\nmaterialPreference = \"2.0.0\"\nstateLayout = \"1.4.2\"\nspannableX = \"1.0.4\"\ndatetime = \"0.6.0-RC.2\"\nserialization = \"1.6.3\"\ngson = \"2.10.1\"\njsoup = \"1.17.2\"\nretrofit = \"2.11.0\"\nconverterGson = \"2.11.0\"\nconverterSerialization = \"1.0.0\"\nokHttp = \"4.12.0\"\ndnsOverHttps = \"4.12.0\"\ncoil = \"2.7.0\"\nglide = \"4.16.0\"\nxPopup = \"2.10.0\"\nxPopupExt = \"1.0.1\"\njiaoziVideoPlayer = \"7.7.2.3300\"\npermissionX = \"1.7.1\"\njunit = \"4.13.2\"\ntestJunit = \"1.2.1\"\ntestEspressoCore = \"3.6.1\"\nguava = \"33.3.1-android\"\nexoplayer = \"1.5.1\"\nleakCanary = \"2.14\"\ncircularRevealSwitch = \"0.4.6\"\ndesugarJdkLibs = \"2.1.5\"\nasynclayoutinflater = \"1.0.0\"\nflexbox = \"3.0.0\"\nfirebaseBom = \"33.10.0\"\ncomposeBom = \"2025.02.00\"\nmpv = \"v0.1.4\"\n\n[libraries]\n# Android\ncore-ktx = { group = \"androidx.core\", name = \"core-ktx\", version.ref = \"coreKtx\" }\nappcompat = { group = \"androidx.appcompat\", name = \"appcompat\", version.ref = \"appCompat\" }\nmaterial = { group = \"com.google.android.material\", name = \"material\", version.ref = \"material\" }\ncoroutines-android = { group = \"org.jetbrains.kotlinx\", name = \"kotlinx-coroutines-android\", version.ref = \"coroutinesAndroid\" }\nconstraintlayout = { group = \"androidx.constraintlayout\", name = \"constraintlayout\", version.ref = \"constraintLayout\" }\nrecyclerview = { group = \"androidx.recyclerview\", name = \"recyclerview\", version.ref = \"recyclerView\" }\nflexbox = { group = \"com.google.android.flexbox\", name = \"flexbox\", version.ref = \"flexbox\" }\n\n# Jetpack\nlifecycle-viewmodel-ktx = { group = \"androidx.lifecycle\", name = \"lifecycle-viewmodel-ktx\", version.ref = \"lifecycleViewModelKtx\" }\nlifecycle-runtime-ktx = { group = \"androidx.lifecycle\", name = \"lifecycle-runtime-ktx\", version.ref = \"lifecycleRuntimeKtx\" }\nlifecycle-livedata-ktx = { group = \"androidx.lifecycle\", name = \"lifecycle-livedata-ktx\", version.ref = \"lifecycleLiveDataKtx\" }\nactivity = { group = \"androidx.activity\", name = \"activity\", version.ref = \"activity\" }\nactivity-ktx = { group = \"androidx.activity\", name = \"activity-ktx\", version.ref = \"activity\" }\nfragment = { group = \"androidx.fragment\", name = \"fragment\", version.ref = \"fragment\" }\nfragment-ktx = { group = \"androidx.fragment\", name = \"fragment-ktx\", version.ref = \"fragment\" }\nroom-runtime = { group = \"androidx.room\", name = \"room-runtime\", version.ref = \"roomRuntime\" }\nroom-ktx = { group = \"androidx.room\", name = \"room-ktx\", version.ref = \"roomKtx\" }\nroom-compiler = { group = \"androidx.room\", name = \"room-compiler\", version.ref = \"roomCompiler\" }\nnavigation-fragment-ktx = { group = \"androidx.navigation\", name = \"navigation-fragment-ktx\", version.ref = \"navigationFragmentKtx\" }\nnavigation-ui-ktx = { group = \"androidx.navigation\", name = \"navigation-ui-ktx\", version.ref = \"navigationUiKtx\" }\npreference-ktx = { group = \"androidx.preference\", name = \"preference-ktx\", version.ref = \"preferenceKtx\" }\nwork-runtime = { group = \"androidx.work\", name = \"work-runtime\", version.ref = \"workRuntime\" }\nwork-runtime-ktx = { group = \"androidx.work\", name = \"work-runtime-ktx\", version.ref = \"workRuntimeKtx\" }\nguava = { group = \"com.google.guava\", name = \"guava\", version.ref = \"guava\" }\nstartup-runtime = { group = \"androidx.startup\", name = \"startup-runtime\", version.ref = \"startupRuntime\" }\npalette = { group = \"androidx.palette\", name = \"palette\", version.ref = \"palette\" }\nasynclayoutinflater = { group = \"androidx.asynclayoutinflater\", name = \"asynclayoutinflater\", version.ref = \"asynclayoutinflater\" } # coreLibraryDesugaring\n\n# Compose\ncompose-compose-bom = { group = \"androidx.compose\", name = \"compose-bom\", version.ref = \"composeBom\" }\ncompose-ui-graphics = { group = \"androidx.compose.ui\", name = \"ui-graphics\" }\nandroidx-activity-compose = { group = \"androidx.activity\", name = \"activity-compose\", version.ref = \"activity\" }\ncompose-material3 = { group = \"androidx.compose.material3\", name = \"material3\" }\ncompose-ui-ui-tooling-preview = { group = \"androidx.compose.ui\", name = \"ui-tooling-preview\" }\ncompose-ui-ui-tooling = { group = \"androidx.compose.ui\", name = \"ui-tooling\" }\n\n# Parse\nserialization-json = { group = \"org.jetbrains.kotlinx\", name = \"kotlinx-serialization-json\", version.ref = \"serialization\" }\ngson = { group = \"com.google.code.gson\", name = \"gson\", version.ref = \"gson\" }\njsoup = { group = \"org.jsoup\", name = \"jsoup\", version.ref = \"jsoup\" }\n\n# Network\nretrofit = { group = \"com.squareup.retrofit2\", name = \"retrofit\", version.ref = \"retrofit\" }\nconverter-gson = { group = \"com.squareup.retrofit2\", name = \"converter-gson\", version.ref = \"converterGson\" }\nconverter-serialization = { group = \"com.jakewharton.retrofit\", name = \"retrofit2-kotlinx-serialization-converter\", version.ref = \"converterSerialization\" }\nokhttp = { group = \"com.squareup.okhttp3\", name = \"okhttp\", version.ref = \"okHttp\" }\nokhttp-dnsoverhttps = { group = \"com.squareup.okhttp3\", name = \"okhttp-dnsoverhttps\", version.ref = \"dnsOverHttps\" }\n\n# Pic\ncoil = { group = \"io.coil-kt\", name = \"coil\", version.ref = \"coil\" }\nglide = { group = \"com.github.bumptech.glide\", name = \"glide\", version.ref = \"glide\" }\n\n# Test\njunit = { group = \"junit\", name = \"junit\", version.ref = \"junit\" } # testImplementation\ntest-junit = { group = \"androidx.test.ext\", name = \"junit\", version.ref = \"testJunit\" } # androidTestImplementation\ntest-espresso-core = { group = \"androidx.test.espresso\", name = \"espresso-core\", version.ref = \"testEspressoCore\" } # androidTestImplementation\n\n# video\njiaozi-video-player = { group = \"cn.jzvd\", name = \"jiaozivideoplayer\", version.ref = \"jiaoziVideoPlayer\" }\nmedia3-exoplayer = { group = \"androidx.media3\", name = \"media3-exoplayer\", version.ref = \"exoplayer\" }\nmedia3-exoplayer-hls = { group = \"androidx.media3\", name = \"media3-exoplayer-hls\", version.ref = \"exoplayer\" }\n\n# Other\nabout = { group = \"com.drakeet.about\", name = \"about\", version.ref = \"about\" }\nexpandable-textview = { group = \"com.github.MZCretin\", name = \"ExpandableTextView\", version.ref = \"expandableTextView\" }\nbase-recyclerview-adapter-helper3 = { group = \"io.github.cymchad\", name = \"BaseRecyclerViewAdapterHelper\", version.ref = \"baseRecyclerViewAdapterHelper3\" }\nbase-recyclerview-adapter-helper4 = { group = \"io.github.cymchad\", name = \"BaseRecyclerViewAdapterHelper4\", version.ref = \"baseRecyclerViewAdapterHelper4\" }\nrefresh-layout-kernel = { group = \"io.github.scwang90\", name = \"refresh-layout-kernel\", version.ref = \"refreshLayoutKernel\" }\nrefresh-header-material = { group = \"io.github.scwang90\", name = \"refresh-header-material\", version.ref = \"refreshHeaderMaterial\" }\nrefresh-footer-classics = { group = \"io.github.scwang90\", name = \"refresh-footer-classics\", version.ref = \"refreshFooterClassics\" }\nmultitype = { group = \"com.drakeet.multitype\", name = \"multitype\", version.ref = \"multiType\" }\nmaterial-preference = { group = \"dev.rikka.rikkax.material\", name = \"material-preference\", version.ref = \"materialPreference\" }\nstatelayout = { group = \"com.github.liangjingkanji\", name = \"StateLayout\", version.ref = \"stateLayout\" }\nspannable-x = { group = \"com.itxca.spannablex\", name = \"spannablex\", version.ref = \"spannableX\" }\ndatetime = { group = \"org.jetbrains.kotlinx\", name = \"kotlinx-datetime\", version.ref = \"datetime\" }\nxpopup = { group = \"com.github.li-xiaojun\", name = \"XPopup\", version.ref = \"xPopup\" }\nxpopup-ext = { group = \"com.github.li-xiaojun\", name = \"XPopupExt\", version.ref = \"xPopupExt\" }\npermission-x = { group = \"com.guolindev.permissionx\", name = \"permissionx\", version.ref = \"permissionX\" }\ncircular-reveal-switch = { group = \"com.github.YenalyLiew\", name = \"CircularRevealSwitch\", version.ref = \"circularRevealSwitch\" }\nleak-canary = { group = \"com.squareup.leakcanary\", name = \"leakcanary-android\", version.ref = \"leakCanary\" } # debugImplementation\ndesugar-jdk-libs = { group = \"com.android.tools\", name = \"desugar_jdk_libs\", version.ref = \"desugarJdkLibs\" }\nkotlin-reflect = { group = \"org.jetbrains.kotlin\", name = \"kotlin-reflect\", version.ref = \"kotlin\" }\n\n# firebase\nfirebase-crashlytics = { group = \"com.google.firebase\", name = \"firebase-crashlytics\" }\nfirebase-analytics = { group = \"com.google.firebase\", name = \"firebase-analytics\" }\nfirebase-perf = { group = \"com.google.firebase\", name = \"firebase-perf\" }\nfirebase-config = { group = \"com.google.firebase\", name = \"firebase-config\" }\nfirebase-bom = { group = \"com.google.firebase\", name = \"firebase-bom\", version.ref = \"firebaseBom\" }\n\n# mpv\nmpv-lib = { group = \"com.github.abdallahmehiz\", name = \"mpv-android\", version.ref = \"mpv\" }\n[bundles]\n\nandroid-base = [\n    \"core-ktx\",\n    \"appcompat\",\n    \"material\",\n    \"coroutines-android\",\n    \"constraintlayout\",\n    \"recyclerview\"\n]\n\nandroid-jetpack = [\n    \"lifecycle-viewmodel-ktx\", \"lifecycle-runtime-ktx\", \"lifecycle-livedata-ktx\",\n    \"activity\", \"activity-ktx\", \"fragment\", \"fragment-ktx\",\n    \"room-runtime\", \"room-ktx\",\n    \"navigation-fragment-ktx\", \"navigation-ui-ktx\",\n    \"preference-ktx\",\n    \"work-runtime\", \"work-runtime-ktx\", \"guava\"\n]\n\n[plugins]\ncom-android-application = { id = \"com.android.application\", version.ref = \"androidGradle\" }\ncom-android-library = { id = \"com.android.library\", version.ref = \"androidGradle\" }\norg-jetbrains-kotlin-android = { id = \"org.jetbrains.kotlin.android\", version.ref = \"kotlin\" }\ncom-google-devtools-ksp = { id = \"com.google.devtools.ksp\", version.ref = \"ksp\" }\norg-jetbrains-kotlin-plugin-serialization = { id = \"org.jetbrains.kotlin.plugin.serialization\", version.ref = \"serializationPlugin\" }\norg-jetbrains-kotlin-plugin-parcelize = { id = \"org.jetbrains.kotlin.plugin.parcelize\", version.ref = \"kotlin\" }\ncom-google-gms-google-services = { id = \"com.google.gms.google-services\", version = \"4.4.2\" }\ncom-google-firebase-crashlytics = { id = \"com.google.firebase.crashlytics\", version = \"3.0.2\" }\ncom-google-firebase-firebase-pref = { id = \"com.google.firebase.firebase-perf\", version = \"1.4.2\" }\ncompose-compiler = { id = \"org.jetbrains.kotlin.plugin.compose\", version.ref = \"kotlin\" }"
  },
  {
    "path": "gradle/wrapper/gradle-wrapper.properties",
    "content": "#Mon May 13 16:01:02 CST 2024\ndistributionBase=GRADLE_USER_HOME\ndistributionPath=wrapper/dists\ndistributionUrl=https\\://services.gradle.org/distributions/gradle-8.11.1-bin.zip\nzipStoreBase=GRADLE_USER_HOME\nzipStorePath=wrapper/dists\n"
  },
  {
    "path": "gradle.properties",
    "content": "# Project-wide Gradle settings.\n# IDE (e.g. Android Studio) users:\n# Gradle settings configured through the IDE *will override*\n# any settings specified in this file.\n# For more details on how to configure your build environment visit\n# http://www.gradle.org/docs/current/userguide/build_environment.html\n# Specifies the JVM arguments used for the daemon process.\n# The setting is particularly useful for tweaking memory settings.\norg.gradle.jvmargs=-Xmx2048m -Dfile.encoding=UTF-8\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# AndroidX package structure to make it clearer which packages are bundled with the\n# Android operating system, and which are packaged with your app\"s APK\n# https://developer.android.com/topic/libraries/support-library/androidx-rn\nandroid.useAndroidX=true\n# Kotlin code style for this project: \"official\" or \"obsolete\":\nkotlin.code.style=official\n# Enables namespacing of each library's R class so that its R class includes only the\n# resources declared in the library itself and none from the library's dependencies,\n# thereby reducing the size of the R class for that library\nandroid.nonTransitiveRClass=true\nandroid.enableJetifier=true\nandroid.nonFinalResIds=false\ncompile.sdk=35\nmin.sdk=24\ntarget.sdk=35"
  },
  {
    "path": "gradlew",
    "content": "#!/usr/bin/env sh\n\n#\n# Copyright 2015 the original author or authors.\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#      https://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##############################################################################\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='\"-Xmx64m\" \"-Xms64m\"'\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\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 or MSYS, switch paths to Windows format before running java\nif [ \"$cygwin\" = \"true\" -o \"$msys\" = \"true\" ] ; then\n    APP_HOME=`cygpath --path --mixed \"$APP_HOME\"`\n    CLASSPATH=`cygpath --path --mixed \"$CLASSPATH\"`\n\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=`expr $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\nexec \"$JAVACMD\" \"$@\"\n"
  },
  {
    "path": "gradlew.bat",
    "content": "@rem\n@rem Copyright 2015 the original author or authors.\n@rem\n@rem Licensed under the Apache License, Version 2.0 (the \"License\");\n@rem you may not use this file except in compliance with the License.\n@rem You may obtain a copy of the License at\n@rem\n@rem      https://www.apache.org/licenses/LICENSE-2.0\n@rem\n@rem Unless required by applicable law or agreed to in writing, software\n@rem distributed under the License is distributed on an \"AS IS\" BASIS,\n@rem WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n@rem See the License for the specific language governing permissions and\n@rem limitations under the License.\n@rem\n\n@if \"%DEBUG%\" == \"\" @echo off\n@rem ##########################################################################\n@rem\n@rem  Gradle startup script for Windows\n@rem\n@rem ##########################################################################\n\n@rem Set local scope for the variables with windows NT shell\nif \"%OS%\"==\"Windows_NT\" setlocal\n\nset DIRNAME=%~dp0\nif \"%DIRNAME%\" == \"\" set DIRNAME=.\nset APP_BASE_NAME=%~n0\nset APP_HOME=%DIRNAME%\n\n@rem Resolve any \".\" and \"..\" in APP_HOME to make it shorter.\nfor %%i in (\"%APP_HOME%\") do set APP_HOME=%%~fi\n\n@rem Add default JVM options here. You can also use JAVA_OPTS and GRADLE_OPTS to pass JVM options to this script.\nset DEFAULT_JVM_OPTS=\"-Xmx64m\" \"-Xms64m\"\n\n@rem Find java.exe\nif defined JAVA_HOME goto findJavaFromJavaHome\n\nset JAVA_EXE=java.exe\n%JAVA_EXE% -version >NUL 2>&1\nif \"%ERRORLEVEL%\" == \"0\" goto execute\n\necho.\necho ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH.\necho.\necho Please set the JAVA_HOME variable in your environment to match the\necho location of your Java installation.\n\ngoto fail\n\n:findJavaFromJavaHome\nset JAVA_HOME=%JAVA_HOME:\"=%\nset JAVA_EXE=%JAVA_HOME%/bin/java.exe\n\nif exist \"%JAVA_EXE%\" goto execute\n\necho.\necho ERROR: JAVA_HOME is set to an invalid directory: %JAVA_HOME%\necho.\necho Please set the JAVA_HOME variable in your environment to match the\necho location of your Java installation.\n\ngoto fail\n\n:execute\n@rem Setup the command line\n\nset CLASSPATH=%APP_HOME%\\gradle\\wrapper\\gradle-wrapper.jar\n\n\n@rem Execute Gradle\n\"%JAVA_EXE%\" %DEFAULT_JVM_OPTS% %JAVA_OPTS% %GRADLE_OPTS% \"-Dorg.gradle.appname=%APP_BASE_NAME%\" -classpath \"%CLASSPATH%\" org.gradle.wrapper.GradleWrapperMain %*\n\n:end\n@rem End local scope for the variables with windows NT shell\nif \"%ERRORLEVEL%\"==\"0\" goto mainEnd\n\n:fail\nrem Set variable GRADLE_EXIT_CONSOLE if you need the _script_ return code instead of\nrem the _cmd.exe /c_ return code!\nif  not \"\" == \"%GRADLE_EXIT_CONSOLE%\" exit 1\nexit /b 1\n\n:mainEnd\nif \"%OS%\"==\"Windows_NT\" endlocal\n\n:omega\n"
  },
  {
    "path": "settings.gradle.kts",
    "content": "pluginManagement {\n    repositories {\n        gradlePluginPortal()\n        google()\n        mavenCentral()\n    }\n}\ndependencyResolutionManagement {\n    repositoriesMode.set(RepositoriesMode.FAIL_ON_PROJECT_REPOS)\n    repositories {\n        google()\n        mavenCentral()\n        maven { url = uri(\"https://jitpack.io/\") }\n    }\n}\nrootProject.name = \"Han1meViewer\"\ninclude(\":app\", \":yenaly_libs\")\n"
  },
  {
    "path": "yenaly_libs/.gitignore",
    "content": "/build"
  },
  {
    "path": "yenaly_libs/build.gradle.kts",
    "content": "plugins {\n    alias(libs.plugins.com.android.library)\n    alias(libs.plugins.org.jetbrains.kotlin.android)\n    alias(libs.plugins.com.google.devtools.ksp)\n}\n\nandroid {\n    compileSdk = property(\"compile.sdk\")?.toString()?.toIntOrNull()\n\n    defaultConfig {\n        minSdk = property(\"min.sdk\")?.toString()?.toIntOrNull()\n\n        testInstrumentationRunner = \"androidx.test.runner.AndroidJUnitRunner\"\n        consumerProguardFiles(\"consumer-rules.pro\")\n    }\n\n    buildFeatures {\n        //noinspection DataBindingWithoutKapt\n        dataBinding = true\n    }\n    compileOptions {\n        sourceCompatibility = JavaVersion.VERSION_21\n        targetCompatibility = JavaVersion.VERSION_21\n    }\n    kotlinOptions {\n        jvmTarget = JavaVersion.VERSION_21.toString()\n        freeCompilerArgs = listOf(\n            \"-opt-in=kotlin.RequiresOptIn\",\n            \"-Xskip-prerelease-check\",\n            \"-opt-in=kotlin.ExperimentalStdlibApi\"\n        )\n    }\n    buildFeatures {\n        buildConfig = true\n    }\n    resourcePrefix = \"yenaly_\"\n    namespace = \"com.yenaly.yenaly_libs\"\n}\n\ndependencies {\n\n    implementation(libs.recyclerview)\n    implementation(libs.core.ktx)\n    implementation(libs.appcompat)\n    implementation(libs.material)\n    implementation(libs.coroutines.android)\n\n    implementation(libs.navigation.fragment.ktx)\n    implementation(libs.lifecycle.livedata.ktx)\n    implementation(libs.lifecycle.viewmodel.ktx)\n    implementation(libs.preference.ktx)\n    implementation(libs.startup.runtime)\n    implementation(libs.gson)\n\n    testImplementation(libs.junit)\n\n    androidTestImplementation(libs.test.junit)\n    androidTestImplementation(libs.test.espresso.core)\n}"
  },
  {
    "path": "yenaly_libs/consumer-rules.pro",
    "content": ""
  },
  {
    "path": "yenaly_libs/proguard-rules.pro",
    "content": "# Add project specific ProGuard rules here.\n# You can control the set of applied configuration files using the\n# proguardFiles setting in build.gradle.kts.\n#\n# For more details, see\n#   http://developer.android.com/guide/developing/tools/proguard.html\n\n# If your project uses WebView with JS, uncomment the following\n# and specify the fully qualified class name to the JavaScript interface\n# class:\n#-keepclassmembers class fqcn.of.javascript.interface.for.webview {\n#   public *;\n#}\n\n# Uncomment this to preserve the line number information for\n# debugging stack traces.\n#-keepattributes SourceFile,LineNumberTable\n\n# If you keep the line number information, uncomment this to\n# hide the original source file name.\n#-renamesourcefileattribute SourceFile\n\n-keepattributes SourceFile, LineNumberTable"
  },
  {
    "path": "yenaly_libs/src/androidTest/java/com/yenaly/yenaly_libs/ExampleInstrumentedTest.kt",
    "content": "package com.yenaly.yenaly_libs\n\nimport androidx.test.platform.app.InstrumentationRegistry\nimport androidx.test.ext.junit.runners.AndroidJUnit4\n\nimport org.junit.Test\nimport org.junit.runner.RunWith\n\nimport org.junit.Assert.*\n\n/**\n * Instrumented test, which will execute on an Android device.\n *\n * See [testing documentation](http://d.android.com/tools/testing).\n */\n@RunWith(AndroidJUnit4::class)\nclass ExampleInstrumentedTest {\n    @Test\n    fun useAppContext() {\n        // Context of the app under test.\n        val appContext = InstrumentationRegistry.getInstrumentation().targetContext\n        assertEquals(\"com.yenaly.yenaly_module.test\", appContext.packageName)\n    }\n}"
  },
  {
    "path": "yenaly_libs/src/main/AndroidManifest.xml",
    "content": "<?xml version=\"1.0\" encoding=\"utf-8\"?>\n<manifest xmlns:android=\"http://schemas.android.com/apk/res/android\"\n    xmlns:tools=\"http://schemas.android.com/tools\">\n\n    <application\n        android:networkSecurityConfig=\"@xml/http_network_config\"\n        tools:ignore=\"UnusedAttribute\">\n        <!-- 暂时交给app层\n        <provider\n            android:name=\"androidx.startup.InitializationProvider\"\n            android:authorities=\"${applicationId}.androidx-startup\"\n            android:exported=\"false\"\n            tools:node=\"merge\">\n            <meta-data\n                android:name=\"com.yenaly.yenaly_libs.base.YenalyInitializer\"\n                android:value=\"androidx.startup\" />\n        </provider>\n        -->\n        <activity\n            android:name=\".base.dialog.YenalyCrashDialogActivity\"\n            android:exported=\"true\">\n\n        </activity>\n    </application>\n\n</manifest>"
  },
  {
    "path": "yenaly_libs/src/main/java/com/yenaly/yenaly_libs/ActivityManager.kt",
    "content": "package com.yenaly.yenaly_libs\n\nimport android.annotation.SuppressLint\nimport android.app.Activity\nimport android.content.Intent\nimport android.util.Log\nimport com.yenaly.yenaly_libs.utils.applicationContext\nimport java.lang.ref.WeakReference\nimport kotlin.system.exitProcess\n\n/**\n * @author Yenaly Liew\n * @time 2023/08/15 015 19:36\n */\n@Suppress(\"unused\")\nobject ActivityManager {\n\n    private const val TAG = \"ActivityManager\"\n\n    @JvmStatic\n    var currentActivity: WeakReference<Activity?> = WeakReference(null)\n        internal set\n\n    private val topActivityByReflect: Activity?\n        @SuppressLint(\"PrivateApi\")\n        get() {\n            try {\n                val activityThreadCls = Class.forName(\"android.app.ActivityThread\")\n                val activityThread = activityThreadCls.getMethod(\"currentActivityThread\")(null)\n                val activitiesField = activityThreadCls.getDeclaredField(\"mActivities\")\n                activitiesField.isAccessible = true\n                val activities = activitiesField[activityThread] as Map<*, *>?\n                if (activities == null) return null\n                activities.values.forEach { activityRecord ->\n                    val activityRecordCls = activityRecord?.javaClass\n                    activityRecordCls?.getDeclaredField(\"paused\")?.also { pausedField ->\n                        pausedField.isAccessible = true\n                        if (!pausedField.getBoolean(activityRecord)) {\n                            val activityField = activityRecordCls.getDeclaredField(\"activity\")\n                            activityField.isAccessible = true\n                            return activityField[activityRecord] as Activity\n                        }\n                    }\n                }\n            } catch (e: Exception) {\n                e.printStackTrace()\n            }\n            return null\n        }\n\n    @JvmStatic\n    fun exit(killProcess: Boolean = true) {\n        if (killProcess) exitProcess(0)\n        Log.i(TAG, \"exit\")\n    }\n\n    @JvmStatic\n    fun restart(killProcess: Boolean = true) {\n        val intent = applicationContext.packageManager\n            .getLaunchIntentForPackage(applicationContext.packageName)\n        // #issue-crashlytics-b39688491e64c6cde89e73f71a9f42a1\n        if (intent != null) {\n            intent.addFlags(Intent.FLAG_ACTIVITY_CLEAR_TOP or Intent.FLAG_ACTIVITY_CLEAR_TASK)\n            applicationContext.startActivity(intent)\n        }\n        if (killProcess) exitProcess(0)\n    }\n}"
  },
  {
    "path": "yenaly_libs/src/main/java/com/yenaly/yenaly_libs/SingletonHolder.kt",
    "content": "package com.yenaly.yenaly_libs\n\n/**\n * @author Yenaly Liew\n * @time 2023/08/29 029 13:58\n */\nabstract class SingleArgSingletonHolder<out T, in A>(private var constructor: ((A) -> T)?) {\n    @Volatile\n    private var instance: T? = null\n    fun getInstance(arg: A): T = instance ?: synchronized(this) {\n        instance ?: constructor!!(arg).also {\n            instance = it\n            constructor = null\n        }\n    }\n}"
  },
  {
    "path": "yenaly_libs/src/main/java/com/yenaly/yenaly_libs/base/IViewBinding.kt",
    "content": "package com.yenaly.yenaly_libs.base\n\nimport android.view.LayoutInflater\nimport android.view.ViewGroup\nimport androidx.databinding.ViewDataBinding\n\ninterface IViewBinding<DB : ViewDataBinding> {\n\n    val binding: DB\n\n    fun getViewBinding(inflater: LayoutInflater, container: ViewGroup?): DB\n\n}"
  },
  {
    "path": "yenaly_libs/src/main/java/com/yenaly/yenaly_libs/base/YenalyActivity.kt",
    "content": "package com.yenaly.yenaly_libs.base\n\nimport android.os.Bundle\nimport android.view.LayoutInflater\nimport android.view.ViewGroup\nimport androidx.databinding.ViewDataBinding\nimport com.yenaly.yenaly_libs.base.frame.FrameActivity\n\n/**\n * @ProjectName : YenalyModule\n * @Author : Yenaly Liew\n * @Time : 2022/04/16 016 20:20\n * @Description : Description...\n */\nabstract class YenalyActivity<DB : ViewDataBinding> : FrameActivity(), IViewBinding<DB> {\n\n    private var _binding: DB? = null\n    override val binding get() = _binding!!\n    val bindingOrNull get() = _binding\n\n    /**\n     * 取代之前的反射方式，太消耗性能了\n     */\n    abstract fun getViewBinding(layoutInflater: LayoutInflater): DB\n\n    final override fun getViewBinding(inflater: LayoutInflater, container: ViewGroup?): DB {\n        return getViewBinding(inflater)\n    }\n\n    override fun onCreate(savedInstanceState: Bundle?) {\n        super.onCreate(savedInstanceState)\n        initView()\n        initData(savedInstanceState)\n        bindDataObservers()\n    }\n\n    override fun onDestroy() {\n        super.onDestroy()\n        _binding?.unbind()\n        // 没啥必要\n        // _binding = null\n    }\n\n    private fun initView() {\n        _binding = getViewBinding(layoutInflater, null)\n        setContentView(binding.root)\n        binding.lifecycleOwner = this\n    }\n\n    /**\n     * 用于绑定数据观察器 (optional)\n     */\n    open fun bindDataObservers() = Unit\n\n    /**\n     * 初始化数据\n     */\n    abstract fun initData(savedInstanceState: Bundle?)\n}"
  },
  {
    "path": "yenaly_libs/src/main/java/com/yenaly/yenaly_libs/base/YenalyApplication.kt",
    "content": "@file:Suppress(\"unused\")\n\npackage com.yenaly.yenaly_libs.base\n\nimport android.app.Activity\nimport android.app.Application\nimport android.os.Bundle\nimport com.yenaly.yenaly_libs.ActivityManager\nimport com.yenaly.yenaly_libs.BuildConfig\nimport java.lang.ref.WeakReference\n\n/**\n * @ProjectName : YenalyModule\n * @Author : Yenaly Liew\n * @Time : 2022/04/16 016 21:52\n * @Description : Description...\n */\nopen class YenalyApplication : Application(), Application.ActivityLifecycleCallbacks {\n\n    open val isDefaultCrashHandlerEnabled: Boolean = true\n\n    override fun onCreate() {\n        super.onCreate()\n        registerActivityLifecycleCallbacks(this)\n        // do not forget to register the crash dialog activity!\n        if (isDefaultCrashHandlerEnabled && !BuildConfig.DEBUG)\n            YenalyCrashHandler.instance.init(this)\n    }\n\n    override fun onActivityCreated(activity: Activity, savedInstanceState: Bundle?) {\n    }\n\n    override fun onActivityStarted(activity: Activity) {\n    }\n\n    override fun onActivityResumed(activity: Activity) {\n        ActivityManager.currentActivity = WeakReference(activity)\n    }\n\n    override fun onActivityPaused(activity: Activity) {\n    }\n\n    override fun onActivityStopped(activity: Activity) {\n    }\n\n    override fun onActivitySaveInstanceState(activity: Activity, outState: Bundle) {\n    }\n\n    override fun onActivityDestroyed(activity: Activity) {\n    }\n}"
  },
  {
    "path": "yenaly_libs/src/main/java/com/yenaly/yenaly_libs/base/YenalyBottomSheetDialogFragment.kt",
    "content": "package com.yenaly.yenaly_libs.base\n\nimport android.app.Dialog\nimport android.os.Bundle\nimport android.view.LayoutInflater\nimport android.view.ViewGroup\nimport androidx.databinding.ViewDataBinding\nimport androidx.fragment.app.Fragment\nimport androidx.fragment.app.FragmentActivity\nimport com.google.android.material.bottomsheet.BottomSheetDialogFragment\nimport com.yenaly.yenaly_libs.R\n\n/**\n * @ProjectName : YenalyModule\n * @Author : Yenaly Liew\n * @Time : 2022/05/04 004 14:46\n * @Description : Description...\n */\nabstract class YenalyBottomSheetDialogFragment<DB : ViewDataBinding> :\n    BottomSheetDialogFragment(), IViewBinding<DB> {\n\n    private var _binding: DB? = null\n    override val binding get() = _binding!!\n\n    final override fun getViewBinding(inflater: LayoutInflater, container: ViewGroup?): DB {\n        return getViewBinding(inflater)\n    }\n\n    override fun onCreate(savedInstanceState: Bundle?) {\n        super.onCreate(savedInstanceState)\n        setStyle()\n    }\n\n    override fun onCreateDialog(savedInstanceState: Bundle?): Dialog {\n        val dialog = super.onCreateDialog(savedInstanceState)\n        val layoutInflater = LayoutInflater.from(context)\n        _binding = getViewBinding(layoutInflater, null)\n        dialog.setContentView(binding.root)\n        initData(savedInstanceState, dialog)\n        return dialog\n    }\n\n    override fun onDestroy() {\n        super.onDestroy()\n        _binding?.unbind()\n    }\n\n    /**\n     * 设置dialog风格 (optional)\n     *\n     * 默认为透明，需要自己在rootView上添加背景\n     */\n    open fun setStyle() {\n        setStyle(STYLE_NORMAL, R.style.YenalyBottomSheetDialog)\n    }\n\n    abstract fun getViewBinding(layoutInflater: LayoutInflater): DB\n\n    /**\n     * 初始化数据\n     */\n    abstract fun initData(savedInstanceState: Bundle?, dialog: Dialog)\n\n    /**\n     * 简化fragment内唤出该dialog的方式\n     */\n    fun showIn(fragment: Fragment) {\n        val fragmentManager = fragment.requireActivity().supportFragmentManager\n        if (fragmentManager.findFragmentByTag(this.javaClass.name) != null) {\n            return\n        }\n        show(fragmentManager, this.javaClass.name)\n    }\n\n    /**\n     * 简化activity内唤出该dialog的方式\n     */\n    fun showIn(activity: FragmentActivity) {\n        val fragmentManager = activity.supportFragmentManager\n        if (fragmentManager.findFragmentByTag(this.javaClass.name) != null) {\n            return\n        }\n        show(fragmentManager, this.javaClass.name)\n    }\n}"
  },
  {
    "path": "yenaly_libs/src/main/java/com/yenaly/yenaly_libs/base/YenalyCrashHandler.kt",
    "content": "package com.yenaly.yenaly_libs.base\n\nimport android.content.Context\nimport android.content.Intent\nimport com.yenaly.yenaly_libs.ActivityManager\nimport com.yenaly.yenaly_libs.base.dialog.YenalyCrashDialogActivity\nimport com.yenaly.yenaly_libs.utils.applicationContext\nimport com.yenaly.yenaly_libs.utils.startActivity\nimport java.io.PrintWriter\nimport java.io.StringWriter\n\n/**\n * @ProjectName : YenalyModule\n * @Author : Yenaly Liew\n * @Time : 2022/04/21 021 21:15\n * @Description : Description...\n */\nclass YenalyCrashHandler private constructor() : Thread.UncaughtExceptionHandler {\n\n    private lateinit var mContext: Context\n    private var mDefaultHandler: Thread.UncaughtExceptionHandler? = null\n\n    companion object {\n        val instance: YenalyCrashHandler by lazy(::YenalyCrashHandler)\n    }\n\n    fun init(context: Context) {\n        this.mContext = context\n        this.mDefaultHandler = Thread.getDefaultUncaughtExceptionHandler()\n        Thread.setDefaultUncaughtExceptionHandler(this)\n    }\n\n    override fun uncaughtException(t: Thread, e: Throwable) {\n        if (!handleError(e) && mDefaultHandler != null) {\n            mDefaultHandler!!.uncaughtException(t, e)\n        } else {\n            val errorWriter = StringWriter()\n            e.printStackTrace(PrintWriter(errorWriter))\n            applicationContext.startActivity<YenalyCrashDialogActivity>(\n                flag = Intent.FLAG_ACTIVITY_NEW_TASK,\n                values = arrayOf(\"yenaly_throwable\" to errorWriter.toString())\n            )\n            ActivityManager.exit(killProcess = true)\n        }\n    }\n\n    private fun handleError(throwable: Throwable?): Boolean {\n        return throwable != null\n    }\n}"
  },
  {
    "path": "yenaly_libs/src/main/java/com/yenaly/yenaly_libs/base/YenalyFragment.kt",
    "content": "package com.yenaly.yenaly_libs.base\n\nimport android.os.Bundle\nimport android.view.LayoutInflater\nimport android.view.View\nimport android.view.ViewGroup\nimport androidx.databinding.ViewDataBinding\nimport com.yenaly.yenaly_libs.base.frame.FrameFragment\n\n/**\n * @ProjectName : YenalyModule\n * @Author : Yenaly Liew\n * @Time : 2022/04/16 016 20:25\n * @Description : Description...\n */\nabstract class YenalyFragment<DB : ViewDataBinding> : FrameFragment(), IViewBinding<DB> {\n\n    private var _binding: DB? = null\n    override val binding get() = _binding!!\n    val bindingOrNull get() = _binding\n\n    override fun onCreateView(\n        inflater: LayoutInflater,\n        container: ViewGroup?,\n        savedInstanceState: Bundle?,\n    ): View {\n        initView(inflater, container)\n        return binding.root\n    }\n\n    override fun onViewCreated(view: View, savedInstanceState: Bundle?) {\n        super.onViewCreated(view, savedInstanceState)\n        initData(savedInstanceState)\n        bindDataObservers()\n    }\n\n    override fun onDestroyView() {\n        super.onDestroyView()\n        _binding?.unbind()\n    }\n\n    private fun initView(\n        inflater: LayoutInflater,\n        container: ViewGroup?,\n    ) {\n        _binding = getViewBinding(inflater, container)\n        binding.lifecycleOwner = viewLifecycleOwner\n    }\n\n    /**\n     * 用于绑定数据观察器 (optional)\n     */\n    open fun bindDataObservers() = Unit\n\n    /**\n     * 初始化数据\n     */\n    abstract fun initData(savedInstanceState: Bundle?)\n}"
  },
  {
    "path": "yenaly_libs/src/main/java/com/yenaly/yenaly_libs/base/YenalyInitializer.kt",
    "content": "@file:Suppress(\"unused\")\n\npackage com.yenaly.yenaly_libs.base\n\nimport android.content.Context\nimport androidx.annotation.CallSuper\nimport androidx.startup.Initializer\nimport com.yenaly.yenaly_libs.utils.applicationContext\n\n/**\n * @ProjectName : YenalyModule\n * @Author : Yenaly Liew\n * @Time : 2022/04/21 021 14:04\n * @Description : Description...\n */\nopen class YenalyInitializer : Initializer<Unit> {\n\n    @CallSuper\n    override fun create(context: Context) {\n        applicationContext = context\n    }\n\n    override fun dependencies(): MutableList<Class<out Initializer<*>>> {\n        return mutableListOf()\n    }\n}"
  },
  {
    "path": "yenaly_libs/src/main/java/com/yenaly/yenaly_libs/base/YenalyViewModel.kt",
    "content": "package com.yenaly.yenaly_libs.base\n\nimport android.app.Application\nimport androidx.lifecycle.AndroidViewModel\nimport com.yenaly.yenaly_libs.utils.unsafeLazy\n\n/**\n * @ProjectName : YenalyModule\n * @Author : Yenaly Liew\n * @Time : 2022/04/20 020 11:37\n * @Description : Description...\n */\nopen class YenalyViewModel(\n    @JvmField protected val application: Application\n) : AndroidViewModel(application) {\n\n    var parent: YenalyViewModel? = null\n        private set\n\n    @Suppress(\"UNCHECKED_CAST\")\n    fun <YVM : YenalyViewModel> parent(): YVM? = parent as? YVM\n\n    fun <YVM : YenalyViewModel> requireParent(): YVM = parent() ?: error(\"Parent not found\")\n\n    inline fun <reified YVM : YenalyViewModel> sub() = sub(YVM::class.java)\n\n    fun <YVM : YenalyViewModel> sub(clazz: Class<YVM>): Lazy<YVM> = unsafeLazy {\n        clazz.getConstructor(Application::class.java).newInstance(application).also {\n            it.parent = this\n        }\n    }\n}"
  },
  {
    "path": "yenaly_libs/src/main/java/com/yenaly/yenaly_libs/base/dialog/YenalyCrashDialogActivity.kt",
    "content": "package com.yenaly.yenaly_libs.base.dialog\n\nimport android.os.Bundle\nimport androidx.core.text.parseAsHtml\nimport com.google.android.material.dialog.MaterialAlertDialogBuilder\nimport com.yenaly.yenaly_libs.ActivityManager\nimport com.yenaly.yenaly_libs.R\nimport com.yenaly.yenaly_libs.base.frame.FrameActivity\nimport com.yenaly.yenaly_libs.utils.copyToClipboard\nimport com.yenaly.yenaly_libs.utils.intentExtra\nimport com.yenaly.yenaly_libs.utils.sp\n\n/**\n * @ProjectName : YenalyModule\n * @Author : Yenaly Liew\n * @Time : 2022/04/21 021 22:23\n * @Description : Description...\n */\nclass YenalyCrashDialogActivity : FrameActivity() {\n\n    private val yenalyThrowable by intentExtra(\"yenaly_throwable\", \"null\")\n\n    override fun setUiStyle() {\n        setTheme(R.style.YenalyDialog)\n    }\n\n    override fun onCreate(savedInstanceState: Bundle?) {\n        super.onCreate(savedInstanceState)\n        setContentView(R.layout.yenaly_activity_crash_dialog)\n        val info =\n            \"\"\"<span style=\"color: #FF0000; font-size: ${18.sp}px;\">These errors occurred:</span><br><br>$yenalyThrowable\"\"\".parseAsHtml()\n        MaterialAlertDialogBuilder(this)\n            .setTitle(R.string.yenaly_error_title)\n            .setMessage(info)\n            .setCancelable(false)\n            .setPositiveButton(R.string.yenaly_restart_app) { _, _ ->\n                ActivityManager.restart(killProcess = true)\n            }\n            .setNegativeButton(R.string.yenaly_exit_app) { _, _ ->\n                ActivityManager.exit(killProcess = true)\n            }\n            .setNeutralButton(R.string.yenaly_copy) { _, _ ->\n                yenalyThrowable.copyToClipboard()\n            }\n            .show()\n    }\n}"
  },
  {
    "path": "yenaly_libs/src/main/java/com/yenaly/yenaly_libs/base/frame/FrameActivity.kt",
    "content": "package com.yenaly.yenaly_libs.base.frame\n\nimport android.os.Bundle\nimport android.util.Log\nimport android.view.Menu\nimport android.view.MenuInflater\nimport android.view.MenuItem\nimport androidx.annotation.MenuRes\nimport androidx.appcompat.app.AppCompatActivity\nimport androidx.core.view.MenuProvider\nimport androidx.fragment.app.Fragment\nimport androidx.fragment.app.FragmentManager\nimport androidx.lifecycle.Lifecycle\nimport androidx.lifecycle.LifecycleOwner\nimport androidx.navigation.fragment.NavHostFragment\n\n/**\n * @author Yenaly Liew\n * @time 2022/04/16 016 20:25\n */\nabstract class FrameActivity : AppCompatActivity() {\n\n    /**\n     * 主题界面风格相关可以在这里设置 (optional)\n     */\n    open fun setUiStyle() {\n    }\n\n    /**\n     * 能够监听该 Activity 旗下所有 Fragment 的 onResume 事件\n     */\n    open val onFragmentResumedListener: ((Fragment) -> Unit)? = null\n\n    override fun onCreate(savedInstanceState: Bundle?) {\n        setUiStyle()\n        super.onCreate(savedInstanceState)\n        if (onFragmentResumedListener != null) {\n            supportFragmentManager.registerFragmentLifecycleCallbacks(object :\n                FragmentManager.FragmentLifecycleCallbacks() {\n                override fun onFragmentResumed(fm: FragmentManager, f: Fragment) {\n                    if (f is NavHostFragment) return\n                    onFragmentResumedListener?.invoke(f)\n                    Log.d(\"FrameActivity\", \"onFragmentResumed: $f\")\n                }\n            }, true)\n        }\n    }\n\n    /**\n     * 快捷构建 Menu\n     *\n     * 使用了最新 API，创建菜单更简单。\n     *\n     * @param menuRes menuRes。\n     * @param action 和 [onOptionsItemSelected] 用法一致。\n     */\n    open fun addMenu(\n        @MenuRes menuRes: Int,\n        action: (menuItem: MenuItem) -> Boolean,\n    ) {\n        addMenuProvider(object : MenuProvider {\n            override fun onCreateMenu(menu: Menu, menuInflater: MenuInflater) {\n                menuInflater.inflate(menuRes, menu)\n            }\n\n            override fun onMenuItemSelected(menuItem: MenuItem): Boolean {\n                return action.invoke(menuItem)\n            }\n        })\n    }\n\n    /**\n     * 详情 [addMenu]\n     */\n    open fun addMenu(\n        @MenuRes menuRes: Int,\n        owner: LifecycleOwner,\n        action: (menuItem: MenuItem) -> Boolean,\n    ) {\n        addMenuProvider(object : MenuProvider {\n            override fun onCreateMenu(menu: Menu, menuInflater: MenuInflater) {\n                menuInflater.inflate(menuRes, menu)\n            }\n\n            override fun onMenuItemSelected(menuItem: MenuItem): Boolean {\n                return action.invoke(menuItem)\n            }\n        }, owner)\n    }\n\n    /**\n     * 详情 [addMenu]\n     */\n    open fun addMenu(\n        @MenuRes menuRes: Int,\n        owner: LifecycleOwner,\n        state: Lifecycle.State,\n        action: (menuItem: MenuItem) -> Boolean,\n    ) {\n        addMenuProvider(object : MenuProvider {\n            override fun onCreateMenu(menu: Menu, menuInflater: MenuInflater) {\n                menuInflater.inflate(menuRes, menu)\n            }\n\n            override fun onMenuItemSelected(menuItem: MenuItem): Boolean {\n                return action.invoke(menuItem)\n            }\n        }, owner, state)\n    }\n}"
  },
  {
    "path": "yenaly_libs/src/main/java/com/yenaly/yenaly_libs/base/frame/FrameFragment.kt",
    "content": "package com.yenaly.yenaly_libs.base.frame\n\nimport android.view.Menu\nimport android.view.MenuInflater\nimport android.view.MenuItem\nimport android.view.Window\nimport androidx.annotation.LayoutRes\nimport androidx.annotation.MenuRes\nimport androidx.core.view.MenuProvider\nimport androidx.fragment.app.Fragment\nimport androidx.lifecycle.Lifecycle\nimport androidx.lifecycle.LifecycleOwner\n\n/**\n * @author : Yenaly Liew\n * @time : 2022/04/16 016 20:25\n */\nabstract class FrameFragment : Fragment {\n\n    constructor() : super()\n    constructor(@LayoutRes resId: Int) : super(resId)\n\n    val window: Window get() = requireActivity().window\n\n    /**\n     * 快捷构建 Menu\n     *\n     * 使用了最新 API，创建菜单更简单。\n     *\n     * @param menuRes menuRes。\n     * @param clear 是否清除从上一界面尤其是 Activity 继承过来的 Menu Item，默认为 true。\n     * @param action 和 [onOptionsItemSelected] 用法一致。\n     */\n    open fun addMenu(\n        @MenuRes menuRes: Int,\n        clear: Boolean = true,\n        action: (menuItem: MenuItem) -> Boolean\n    ) {\n        requireActivity().addMenuProvider(object : MenuProvider {\n            override fun onCreateMenu(menu: Menu, menuInflater: MenuInflater) {\n                // (Optional) 加入 menu.clear() 的原因是，若不加入，如果 Activity 有构建过 Menu，切换到\n                // Fragment 会导致 Activity 的 Menu 继承到 Fragment 里，clear 一下\n                // 可以使得该 Fragment 能使用自己唯一的 Menu.\n                // Fragment 之间的 Menu 问题只需要通过指定 lifecycle 就能搞定，用下面的方法。\n                if (clear) menu.clear()\n                menuInflater.inflate(menuRes, menu)\n            }\n\n            override fun onMenuItemSelected(menuItem: MenuItem): Boolean {\n                return action.invoke(menuItem)\n            }\n        })\n    }\n\n    /**\n     * 详情 [addMenu]，最好使用该带 lifecycleOwner 的方法。\n     */\n    open fun addMenu(\n        @MenuRes menuRes: Int,\n        owner: LifecycleOwner,\n        clear: Boolean = true,\n        action: (menuItem: MenuItem) -> Boolean\n    ) {\n        requireActivity().addMenuProvider(object : MenuProvider {\n            override fun onCreateMenu(menu: Menu, menuInflater: MenuInflater) {\n                if (clear) menu.clear()\n                menuInflater.inflate(menuRes, menu)\n            }\n\n            override fun onMenuItemSelected(menuItem: MenuItem): Boolean {\n                return action.invoke(menuItem)\n            }\n        }, owner)\n    }\n\n    /**\n     * 详情 [addMenu]\n     */\n    open fun addMenu(\n        @MenuRes menuRes: Int,\n        owner: LifecycleOwner,\n        state: Lifecycle.State,\n        clear: Boolean = true,\n        action: (menuItem: MenuItem) -> Boolean\n    ) {\n        requireActivity().addMenuProvider(object : MenuProvider {\n            override fun onCreateMenu(menu: Menu, menuInflater: MenuInflater) {\n                if (clear) menu.clear()\n                menuInflater.inflate(menuRes, menu)\n            }\n\n            override fun onMenuItemSelected(menuItem: MenuItem): Boolean {\n                return action.invoke(menuItem)\n            }\n        }, owner, state)\n    }\n\n    /**\n     * 清除 Menu 所有元素\n     */\n    open fun clearMenu() {\n        requireActivity().addMenuProvider(object : MenuProvider {\n            override fun onCreateMenu(menu: Menu, menuInflater: MenuInflater) {\n                menu.clear()\n            }\n\n            override fun onMenuItemSelected(menuItem: MenuItem): Boolean {\n                return false\n            }\n        }, viewLifecycleOwner)\n    }\n}"
  },
  {
    "path": "yenaly_libs/src/main/java/com/yenaly/yenaly_libs/base/preference/LongClickablePreference.kt",
    "content": "package com.yenaly.yenaly_libs.base.preference\n\nimport android.content.Context\nimport android.util.AttributeSet\nimport androidx.preference.Preference\nimport androidx.preference.PreferenceViewHolder\n\n/**\n * @project Han1meViewer\n * @author Yenaly Liew\n * @time 2022/08/25 025 23:26\n */\nopen class LongClickablePreference @JvmOverloads constructor(\n    context: Context, attrs: AttributeSet? = null,\n) : Preference(context, attrs) {\n\n    private var onPreferenceLongClickListener: OnPreferenceLongClickListener? = null\n\n    override fun onBindViewHolder(holder: PreferenceViewHolder) {\n        super.onBindViewHolder(holder)\n        holder.itemView.setOnLongClickListener {\n            performLongClick()\n        }\n    }\n\n    fun setOnPreferenceLongClickListener(onPreferenceLongClickListener: OnPreferenceLongClickListener) {\n        this.onPreferenceLongClickListener = onPreferenceLongClickListener\n    }\n\n    private fun performLongClick(): Boolean {\n        if (!isEnabled || !isSelectable) {\n            return false\n        }\n        return onPreferenceLongClickListener?.onPreferenceLongClick(this) == true\n    }\n\n    fun interface OnPreferenceLongClickListener {\n        fun onPreferenceLongClick(preference: Preference): Boolean\n    }\n}"
  },
  {
    "path": "yenaly_libs/src/main/java/com/yenaly/yenaly_libs/base/preference/LongClickableSwitchPreference.kt",
    "content": "package com.yenaly.yenaly_libs.base.preference\n\nimport android.content.Context\nimport android.util.AttributeSet\nimport androidx.preference.Preference\nimport androidx.preference.PreferenceViewHolder\n\nopen class LongClickableSwitchPreference(\n    context: Context, attrs: AttributeSet? = null,\n) : MaterialSwitchPreference(context, attrs) {\n    private var onPreferenceLongClickListener: OnPreferenceLongClickListener? = null\n\n    override fun onBindViewHolder(holder: PreferenceViewHolder) {\n        super.onBindViewHolder(holder)\n        holder.itemView.setOnLongClickListener {\n            performLongClick()\n        }\n    }\n\n    fun setOnPreferenceLongClickListener(onPreferenceLongClickListener: OnPreferenceLongClickListener) {\n        this.onPreferenceLongClickListener = onPreferenceLongClickListener\n    }\n\n    private fun performLongClick(): Boolean {\n        if (!isEnabled || !isSelectable) {\n            return false\n        }\n        return onPreferenceLongClickListener?.onPreferenceLongClick(this) == true\n    }\n\n    fun interface OnPreferenceLongClickListener {\n        fun onPreferenceLongClick(preference: Preference): Boolean\n    }\n}"
  },
  {
    "path": "yenaly_libs/src/main/java/com/yenaly/yenaly_libs/base/preference/MaterialSwitchPreference.kt",
    "content": "package com.yenaly.yenaly_libs.base.preference\n\nimport android.content.Context\nimport android.util.AttributeSet\nimport androidx.preference.SwitchPreferenceCompat\nimport com.yenaly.yenaly_libs.R\n\nopen class MaterialSwitchPreference @JvmOverloads constructor(\n    context: Context, attrs: AttributeSet? = null,\n) : SwitchPreferenceCompat(context, attrs) {\n    init {\n        widgetLayoutResource = R.layout.yenaly_preference_switch_widget\n    }\n}"
  },
  {
    "path": "yenaly_libs/src/main/java/com/yenaly/yenaly_libs/base/settings/YenalySettingsActivity.kt",
    "content": "package com.yenaly.yenaly_libs.base.settings\n\nimport android.os.Bundle\nimport androidx.databinding.DataBindingUtil\nimport com.yenaly.yenaly_libs.R\nimport com.yenaly.yenaly_libs.base.frame.FrameActivity\nimport com.yenaly.yenaly_libs.databinding.YenalySettingsDataBinding\n\n/**\n * @ProjectName : YenalyModule\n * @Author : Yenaly Liew\n * @Time : 2022/04/17 017 17:13\n * @Description : Description...\n */\nabstract class YenalySettingsActivity : FrameActivity() {\n\n    lateinit var binding: YenalySettingsDataBinding\n\n    override fun onCreate(savedInstanceState: Bundle?) {\n        super.onCreate(savedInstanceState)\n        binding = DataBindingUtil.setContentView(this, R.layout.yenaly_activity_settings)\n        binding.lifecycleOwner = this\n        supportActionBar?.hide()\n        setSupportActionBar(binding.settingsToolbar)\n        binding.settingsToolbar.setNavigationOnClickListener {\n            onBackPressedDispatcher.onBackPressed()\n        }\n        supportActionBar?.setDisplayHomeAsUpEnabled(true)\n        if (savedInstanceState == null) {\n            supportFragmentManager\n                .beginTransaction()\n                .replace(R.id.settings_container_view, initFragmentContainer())\n                .commit()\n        }\n    }\n\n    override fun onDestroy() {\n        super.onDestroy()\n        if (::binding.isInitialized) {\n            binding.unbind()\n        }\n    }\n\n    /**\n     * 初始化设置的Fragment\n     */\n    abstract fun initFragmentContainer(): YenalySettingsFragment\n}"
  },
  {
    "path": "yenaly_libs/src/main/java/com/yenaly/yenaly_libs/base/settings/YenalySettingsFragment.kt",
    "content": "package com.yenaly.yenaly_libs.base.settings\n\nimport android.graphics.drawable.Drawable\nimport android.os.Bundle\nimport androidx.annotation.XmlRes\nimport androidx.preference.Preference\nimport androidx.preference.PreferenceFragmentCompat\nimport com.yenaly.yenaly_libs.utils.unsafeLazy\n\n/**\n * @ProjectName : YenalyModule\n * @Author : Yenaly Liew\n * @Time : 2022/04/17 017 19:26\n * @Description : Description...\n */\nabstract class YenalySettingsFragment(@XmlRes private val xmlRes: Int) :\n    PreferenceFragmentCompat() {\n\n    override fun onCreatePreferences(savedInstanceState: Bundle?, rootKey: String?) {\n        setPreferencesFromResource(xmlRes, rootKey)\n        initPreferencesVariable()\n        onPreferencesCreated(savedInstanceState)\n        bindDataObservers()\n    }\n\n    override fun setDivider(divider: Drawable?) {\n        super.setDivider(null)\n    }\n\n    /**\n     * 用于绑定数据观察器 (optional)\n     */\n    open fun bindDataObservers() = Unit\n\n    /**\n     * 在此处使用[findPreference]初始化设置中的变量\n     */\n    open fun initPreferencesVariable() = Unit\n\n    /**\n     * 界面与xml设置列表绑定后从此处进行view操作\n     */\n    abstract fun onPreferencesCreated(savedInstanceState: Bundle?)\n\n    /**\n     * 快速獲得隸屬於某[key]的Preference，可以爲null\n     */\n    fun <T : Preference> preference(key: String) = unsafeLazy { findPreference<T>(key) }\n\n    /**\n     * 快速獲得隸屬於某[key]的Preference，不可以爲null\n     */\n    fun <T : Preference> safePreference(key: String) = unsafeLazy {\n        checkNotNull(findPreference<T>(key)) {\n            \"The preference belonged to the key \\\"$key\\\" is null.\"\n        }\n    }\n}"
  },
  {
    "path": "yenaly_libs/src/main/java/com/yenaly/yenaly_libs/base/view/NestedScrollableHost.kt",
    "content": "/*\n * Copyright 2019 The Android Open Source Project\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\npackage com.yenaly.yenaly_libs.base.view\n\nimport android.content.Context\nimport android.util.AttributeSet\nimport android.view.MotionEvent\nimport android.view.View\nimport android.view.ViewConfiguration\nimport android.widget.FrameLayout\nimport androidx.viewpager2.widget.ViewPager2\nimport androidx.viewpager2.widget.ViewPager2.ORIENTATION_HORIZONTAL\nimport kotlin.math.absoluteValue\nimport kotlin.math.sign\n\n/**\n * Layout to wrap a scrollable component inside a ViewPager2. Provided as a solution to the problem\n * where pages of ViewPager2 have nested scrollable elements that scroll in the same direction as\n * ViewPager2. The scrollable element needs to be the immediate and only child of this host layout.\n *\n * This solution has limitations when using multiple levels of nested scrollable elements\n * (e.g. a horizontal RecyclerView in a vertical RecyclerView in a horizontal ViewPager2).\n */\nclass NestedScrollableHost : FrameLayout {\n    constructor(context: Context) : super(context)\n    constructor(context: Context, attrs: AttributeSet?) : super(context, attrs)\n\n    private var touchSlop = 0\n    private var initialX = 0f\n    private var initialY = 0f\n    private val parentViewPager: ViewPager2?\n        get() {\n            var v: View? = parent as? View\n            while (v != null && v !is ViewPager2) {\n                v = v.parent as? View\n            }\n            return v\n        }\n\n    private val child: View? get() = if (childCount > 0) getChildAt(0) else null\n\n    init {\n        touchSlop = ViewConfiguration.get(context).scaledTouchSlop\n    }\n\n    private fun canChildScroll(orientation: Int, delta: Float): Boolean {\n        val direction = -delta.sign.toInt()\n        return when (orientation) {\n            0 -> child?.canScrollHorizontally(direction) ?: false\n            1 -> child?.canScrollVertically(direction) ?: false\n            else -> throw IllegalArgumentException()\n        }\n    }\n\n    override fun onInterceptTouchEvent(e: MotionEvent): Boolean {\n        handleInterceptTouchEvent(e)\n        return super.onInterceptTouchEvent(e)\n    }\n\n    private fun handleInterceptTouchEvent(e: MotionEvent) {\n        val orientation = parentViewPager?.orientation ?: return\n\n        // Early return if child can't scroll in same direction as parent\n        if (!canChildScroll(orientation, -1f) && !canChildScroll(orientation, 1f)) {\n            return\n        }\n\n        if (e.action == MotionEvent.ACTION_DOWN) {\n            initialX = e.x\n            initialY = e.y\n            parent.requestDisallowInterceptTouchEvent(true)\n        } else if (e.action == MotionEvent.ACTION_MOVE) {\n            val dx = e.x - initialX\n            val dy = e.y - initialY\n            val isVpHorizontal = orientation == ORIENTATION_HORIZONTAL\n\n            // assuming ViewPager2 touch-slop is 2x touch-slop of child\n            val scaledDx = dx.absoluteValue * if (isVpHorizontal) .5f else 1f\n            val scaledDy = dy.absoluteValue * if (isVpHorizontal) 1f else .5f\n\n            if (scaledDx > touchSlop || scaledDy > touchSlop) {\n                if (isVpHorizontal == (scaledDy > scaledDx)) {\n                    // Gesture is perpendicular, allow all parents to intercept\n                    parent.requestDisallowInterceptTouchEvent(false)\n                } else {\n                    // Gesture is parallel, query child if movement in that direction is possible\n                    if (canChildScroll(orientation, if (isVpHorizontal) dx else dy)) {\n                        // Child can scroll, disallow all parents to intercept\n                        parent.requestDisallowInterceptTouchEvent(true)\n                    } else {\n                        // Child cannot scroll, allow all parents to intercept\n                        parent.requestDisallowInterceptTouchEvent(false)\n                    }\n                }\n            }\n        }\n    }\n}"
  },
  {
    "path": "yenaly_libs/src/main/java/com/yenaly/yenaly_libs/base/view/RecyclerViewAtViewPager2.kt",
    "content": "package com.yenaly.yenaly_libs.base.view\n\nimport android.content.Context\nimport android.util.AttributeSet\nimport android.view.MotionEvent\nimport androidx.recyclerview.widget.RecyclerView\nimport kotlin.math.abs\n\n/**\n * 重写RecyclerView，解决横向RecyclerView和ViewPager2的滑动冲突。\n */\nclass RecyclerViewAtViewPager2 : RecyclerView {\n\n    constructor(context: Context) : super(context)\n\n    constructor(context: Context, attrs: AttributeSet?) : super(context, attrs)\n\n    constructor(context: Context, attrs: AttributeSet?, defStyleAttr: Int) : super(\n        context,\n        attrs,\n        defStyleAttr\n    )\n\n    private var disallowIntercept = false\n\n    private var startX = 0\n    private var startY = 0\n//    override fun dispatchTouchEvent(ev: MotionEvent): Boolean {\n//        when (ev.action) {\n//            MotionEvent.ACTION_DOWN -> {\n//                startX = ev.x.toInt()\n//                startY = ev.y.toInt()\n//                parent.requestDisallowInterceptTouchEvent(true)\n//            }\n//\n//            MotionEvent.ACTION_MOVE -> {\n//                val endX = ev.x.toInt()\n//                val endY = ev.y.toInt()\n//                val disX = abs(endX - startX)\n//                val disY = abs(endY - startY)\n//                if (disX > disY) {\n//                    //为了解决RecyclerView嵌套RecyclerView时横向滑动的问题\n//                    if (disallowIntercept) {\n//                        parent.requestDisallowInterceptTouchEvent(disallowIntercept)\n//                    } else {\n//                        parent.requestDisallowInterceptTouchEvent(canScrollHorizontally(startX - endX))\n//                    }\n//                } else {\n//                    parent.requestDisallowInterceptTouchEvent(false)\n//                }\n//            }\n//\n//            MotionEvent.ACTION_UP, MotionEvent.ACTION_CANCEL -> {\n//                parent.requestDisallowInterceptTouchEvent(false)\n//            }\n//        }\n//        return super.dispatchTouchEvent(ev)\n//    }\n//\n//    override fun requestDisallowInterceptTouchEvent(disallowIntercept: Boolean) {\n//        this.disallowIntercept = disallowIntercept\n//        super.requestDisallowInterceptTouchEvent(disallowIntercept)\n//    }\n}"
  },
  {
    "path": "yenaly_libs/src/main/java/com/yenaly/yenaly_libs/utils/ActivityResultUtil.kt",
    "content": "@file:JvmName(\"ActivityResultUtil\")\n\npackage com.yenaly.yenaly_libs.utils\n\nimport android.content.Context\nimport android.content.pm.PackageManager\nimport androidx.activity.result.ActivityResultLauncher\nimport androidx.activity.result.contract.ActivityResultContract\nimport androidx.activity.result.contract.ActivityResultContracts\nimport androidx.core.content.ContextCompat\nimport androidx.lifecycle.Lifecycle\nimport androidx.lifecycle.LifecycleEventObserver\nimport androidx.lifecycle.LifecycleOwner\nimport kotlinx.coroutines.Dispatchers\nimport kotlinx.coroutines.withContext\nimport java.util.concurrent.atomic.AtomicInteger\nimport kotlin.coroutines.Continuation\nimport kotlin.coroutines.resume\nimport kotlin.coroutines.suspendCoroutine\n\n// Thanks to https://github.com/FooIbar/EhViewer/\n\n// 为了让权限申请更轻松，主要是为了能全局控制，\n// 不得不使用了 ActivitiesManager.currentActivity 这个全局变量代替原有 Context\n// 所以使用的时候务必注意！\n\nprivate val atomicInteger = AtomicInteger()\n\nsuspend fun <I, O> Context.awaitActivityResult(\n    contract: ActivityResultContract<I, O>,\n    input: I,\n): O {\n    val key = \"activity_rq#${atomicInteger.getAndIncrement()}\"\n\n    val activity = this.requireComponentActivity()\n    val lifecycle = activity.lifecycle\n    var launcher: ActivityResultLauncher<I>? = null\n    val observer: LifecycleEventObserver = object : LifecycleEventObserver {\n        override fun onStateChanged(source: LifecycleOwner, event: Lifecycle.Event) {\n            if (Lifecycle.Event.ON_DESTROY === event) {\n                launcher?.unregister()\n                lifecycle.removeObserver(this)\n            }\n        }\n    }\n\n    return withContext(Dispatchers.Main) {\n        lifecycle.addObserver(observer)\n        suspendCoroutine(object : Function1<Continuation<O>, Unit> {\n            private var resumed = false\n            override fun invoke(cont: Continuation<O>) {\n                // #issue-crashlytics-7b7eaa428e2541056ce949dff5fe4c55\n                launcher = activity.activityResultRegistry.register(key, contract) {\n                    if (!resumed) {\n                        resumed = true\n                        launcher?.unregister()\n                        lifecycle.removeObserver(observer)\n                        cont.resume(it)\n                    }\n                }.apply { launch(input) }\n            }\n        })\n    }\n}\n\nsuspend fun Context.requestPermission(key: String): Boolean {\n    if (ContextCompat.checkSelfPermission(\n            this, key\n        ) == PackageManager.PERMISSION_GRANTED\n    ) return true\n    return awaitActivityResult(ActivityResultContracts.RequestPermission(), key)\n}"
  },
  {
    "path": "yenaly_libs/src/main/java/com/yenaly/yenaly_libs/utils/AppUtil.kt",
    "content": "@file:JvmName(\"AppUtil\")\n\npackage com.yenaly.yenaly_libs.utils\n\nimport android.content.pm.ApplicationInfo\nimport androidx.core.content.pm.PackageInfoCompat\n\n/**\n * 获取APP名称\n */\nval appName: String\n    get() = applicationContext.applicationInfo\n        .loadLabel(applicationContext.packageManager).toString()\n\n/**\n * 获取本地APP版本号，获取失败则返回null\n *\n * @return 版本号，例如 1.0.0\n */\nval appLocalVersionName: String?\n    get() {\n        return applicationContext.packageManager.getPackageInfo(\n            applicationContext.packageName, 0\n        ).versionName\n    }\n\n/**\n * 获取本地APP版本代码，获取失败则返回0\n *\n * @return 版本代码，例如 12314355\n */\nval appLocalVersionCode: Long\n    get() {\n        val packageInfo = applicationContext.packageManager.getPackageInfo(\n            applicationContext.packageName, 0\n        )\n        return PackageInfoCompat.getLongVersionCode(packageInfo)\n    }\n\n/**\n * 获取APP可及屏幕宽度\n */\nval appScreenWidth: Int get() = applicationContext.resources.displayMetrics.widthPixels\n\n/**\n * 获取APP可及屏幕高度\n */\nval appScreenHeight: Int get() = applicationContext.resources.displayMetrics.heightPixels\n\n/**\n * 判断当前是否为DEBUG模式\n *\n * @return true if debuggable\n */\n@Deprecated(\"Use BuildConfig.DEBUG instead\", ReplaceWith(\"BuildConfig.DEBUG\"))\nval isDebugEnabled: Boolean\n    get() = 0 != applicationContext.applicationInfo.flags and ApplicationInfo.FLAG_DEBUGGABLE"
  },
  {
    "path": "yenaly_libs/src/main/java/com/yenaly/yenaly_libs/utils/ArrayUtil.kt",
    "content": "@file:JvmName(\"ArrayUtil\")\n\npackage com.yenaly.yenaly_libs.utils\n\nimport java.util.stream.Stream\n\n/**\n * 将 IntArray 转化为 StringArray，不经过 List\n */\nfun IntArray.toStringArray(radix: Int = 10): Array<String> {\n    return Array(size) { get(it).toString(radix) }\n}\n\n/**\n * 将 LongArray 转化为 StringArray，不经过 List\n */\nfun LongArray.toStringArray(radix: Int = 10): Array<String> {\n    return Array(size) { get(it).toString(radix) }\n}\n\n/**\n * 将 LongArray 转化为 StringArray，不经过 List\n */\nfun FloatArray.toStringArray(): Array<String> {\n    return Array(size) { get(it).toString() }\n}\n\n/**\n * 将 DoubleArray 转化为 StringArray，不经过 List\n */\nfun DoubleArray.toStringArray(): Array<String> {\n    return Array(size) { get(it).toString() }\n}\n\n/**\n * Kotlin 专属 Stream toArray 语法糖。\n */\ninline fun <reified T> Stream<*>.toTypedArray(): Array<T?> =\n    toArray { size -> arrayOfNulls<T>(size) }\n\n/**\n * 将 List 直接转化为 Array\n */\ninline fun <I, reified O> List<I>.mapToArray(transform: (I) -> O): Array<O> {\n    return Array(size) { i -> transform(this[i]) }\n}\n"
  },
  {
    "path": "yenaly_libs/src/main/java/com/yenaly/yenaly_libs/utils/Base64Util.kt",
    "content": "@file:Suppress(\"unused\")\n@file:JvmName(\"Base64Util\")\n\npackage com.yenaly.yenaly_libs.utils\n\nimport android.util.Base64\n\n/**\n * Base64加密\n */\n@JvmName(\"encodeToString\")\nfun String.encodeToStringByBase64(flag: Int = Base64.DEFAULT): String {\n    return Base64.encodeToString(this.toByteArray(), flag)\n}\n\n/**\n * Base64解密\n */\n@JvmName(\"decodeFromString\")\nfun String.decodeFromStringByBase64(flag: Int = Base64.DEFAULT): String {\n    return String(Base64.decode(this.toByteArray(), flag))\n}\n\n/**\n * Base64解密\n */\n@JvmName(\"decodeFromByteArray\")\nfun ByteArray.decodeFromByteArrayByBase64(flag: Int = Base64.DEFAULT): ByteArray {\n    return Base64.decode(this, flag)\n}"
  },
  {
    "path": "yenaly_libs/src/main/java/com/yenaly/yenaly_libs/utils/ClipboardUtil.kt",
    "content": "@file:JvmName(\"ClipboardUtil\")\n\npackage com.yenaly.yenaly_libs.utils\n\nimport android.content.ClipData\nimport android.content.ClipboardManager\nimport androidx.core.content.getSystemService\nimport kotlinx.coroutines.channels.awaitClose\nimport kotlinx.coroutines.flow.Flow\nimport kotlinx.coroutines.flow.callbackFlow\nimport kotlinx.coroutines.flow.distinctUntilChanged\n\n/**\n * 将文字复制到剪切板\n *\n * @param text    要复制的文字\n * @param label   为此文字设置的用户可见的标签 (optional)\n */\nfun copyTextToClipboard(\n    text: CharSequence?,\n    label: CharSequence? = null,\n) {\n    val clipboardManager = applicationContext.getSystemService<ClipboardManager>()\n    val clipData = ClipData.newPlainText(label, text)\n    clipboardManager?.setPrimaryClip(clipData)\n}\n\n@JvmSynthetic\nfun CharSequence?.copyToClipboard(label: CharSequence? = null) = copyTextToClipboard(this, label)\n\nval textsFromClipboard: Sequence<CharSequence?>\n    get() = sequence {\n        val context = applicationContext\n        val clipboardManager = context.getSystemService<ClipboardManager>()\n        val clipData = clipboardManager?.primaryClip ?: return@sequence\n        for (i in 0..<clipData.itemCount) {\n            clipData.getItemAt(i)?.coerceToText(context)?.let { str ->\n                yield(str)\n            }\n        }\n    }\n\n/**\n * 剪贴板中最近一次的内容\n *\n * @return 剪贴板中最近一次的内容\n */\nval textFromClipboard: CharSequence?\n    get() {\n        val context = applicationContext\n        val clipboardManager = context.getSystemService<ClipboardManager>()\n        val clipData = clipboardManager?.primaryClip ?: return null\n        if (clipData.itemCount > 0) {\n            clipData.getItemAt(0)?.let { item ->\n                return item.coerceToText(context)\n            }\n        }\n        return null\n    }\n\n/**\n * 清除剪切板内容\n */\nfun clearClipboard() {\n    val clipboardManager = applicationContext.getSystemService<ClipboardManager>()\n    val clipData = ClipData.newPlainText(null, null)\n    clipboardManager?.setPrimaryClip(clipData)\n}\n\n/**\n * 监听剪切板内容变化\n */\nfun clipboardFlow(distinct: Boolean): Flow<Sequence<CharSequence?>> {\n    return callbackFlow {\n        val clipboardManager = applicationContext.getSystemService<ClipboardManager>()\n        val listener = ClipboardManager.OnPrimaryClipChangedListener {\n            trySend(textsFromClipboard)\n        }\n        clipboardManager?.addPrimaryClipChangedListener(listener)\n        awaitClose { clipboardManager?.removePrimaryClipChangedListener(listener) }\n    }.run {\n        if (distinct) {\n            distinctUntilChanged { old, new ->\n                val oldIterator = old.iterator()\n                val newIterator = new.iterator()\n                while (oldIterator.hasNext() && newIterator.hasNext()) {\n                    if (oldIterator.next() != newIterator.next()) {\n                        return@distinctUntilChanged false\n                    }\n                }\n                oldIterator.hasNext() == newIterator.hasNext()\n            }\n        } else {\n            this\n        }\n    }\n}"
  },
  {
    "path": "yenaly_libs/src/main/java/com/yenaly/yenaly_libs/utils/ContextUtil.kt",
    "content": "@file:Suppress(\"unused\")\n@file:JvmName(\"ContextUtil\")\n\npackage com.yenaly.yenaly_libs.utils\n\nimport android.app.Activity\nimport android.app.Application\nimport android.content.Context\nimport android.content.ContextWrapper\nimport android.graphics.Color\nimport androidx.activity.ComponentActivity\nimport androidx.annotation.AttrRes\nimport androidx.annotation.ColorInt\nimport androidx.fragment.app.Fragment\nimport androidx.lifecycle.Lifecycle\nimport androidx.lifecycle.LifecycleOwner\nimport com.google.android.material.color.MaterialColors\nimport com.yenaly.yenaly_libs.ActivityManager\n\n/**\n * Global application context.\n */\n@set:JvmSynthetic\nlateinit var applicationContext: Context\n    internal set\n\n/**\n * Get the application instance.\n */\nval application get() = applicationContext as Application\n\n/**\n * Extension property to get the Activity from a Context.\n */\nval Context.activity: Activity?\n    get() {\n        var context = this\n        while (context is ContextWrapper) {\n            if (context is Activity) {\n                return context\n            }\n            context = context.baseContext\n        }\n        return null\n    }\n\n/**\n * Extension function to find an Activity of a specific type from a Context.\n */\ninline fun <reified T : Activity> Context.findActivity(): T {\n    return findActivityOrNull() ?: error(\"No activity of type ${T::class.java.simpleName} found\")\n}\n\n/**\n * Extension function to find an Activity of a specific type from a Context.\n */\ninline fun <reified T : Activity> Context.findActivityOrNull(): T? {\n    var context = this\n    while (context is ContextWrapper) {\n        if (context is T) {\n            return context\n        }\n        context = context.baseContext\n    }\n    return null\n}\n\n/**\n * Extension function to get the Activity from a Fragment.\n */\n@Suppress(\"UNCHECKED_CAST\", \"NOTHING_TO_INLINE\")\ninline fun <T : Activity> Fragment.activity(): T = requireActivity() as T\n\n/**\n * Extension function to require an Activity from a Context.\n * This is mainly used for AlertDialogs which require a Context with a window token.\n */\nfun Context.requireActivity(): Activity =\n    this.activity\n        ?: ActivityManager.currentActivity.get()\n        ?: error(\"No Activity found\")\n\n/**\n * Extension function to get the ComponentActivity from a Context.\n * This is mainly used for AlertDialogs which require a Context with a window token.\n */\nfun Context.requireComponentActivity() =\n    (this.activity ?: ActivityManager.currentActivity.get()) as? ComponentActivity\n        ?: error(\"No ComponentActivity found\")\n\n/**\n * Extension property to get the Lifecycle from a Context.\n */\nval Context.lifecycle: Lifecycle\n    get() {\n        var context: Context? = this\n        while (true) {\n            when (context) {\n                is LifecycleOwner -> return context.lifecycle\n                !is ContextWrapper -> error(\"This should never happen!\")\n                else -> context = context.baseContext\n            }\n        }\n    }\n\n/**\n * Extension function to get a theme color from a Context.\n */\n@ColorInt\nfun Context.getThemeColor(\n    @AttrRes attrColor: Int,\n    @ColorInt defColor: Int = Color.TRANSPARENT,\n): Int {\n    return MaterialColors.getColor(this, attrColor, defColor)\n}"
  },
  {
    "path": "yenaly_libs/src/main/java/com/yenaly/yenaly_libs/utils/DeviceUtil.kt",
    "content": "package com.yenaly.yenaly_libs.utils\n\nimport android.annotation.SuppressLint\nimport android.bluetooth.BluetoothManager\nimport android.bluetooth.BluetoothProfile\nimport android.content.res.Configuration\nimport android.media.AudioDeviceInfo\nimport android.media.AudioManager\nimport android.os.Build\nimport androidx.annotation.RequiresPermission\nimport androidx.core.content.getSystemService\n\nobject DeviceUtil {\n    val isTablet: Boolean\n        get() = applicationContext.resources.configuration.screenLayout and Configuration.SCREENLAYOUT_SIZE_MASK >= Configuration.SCREENLAYOUT_SIZE_LARGE\n\n    val isBluetoothConnected: Boolean\n        @RequiresPermission(android.Manifest.permission.BLUETOOTH_CONNECT) get() {\n            val bm = applicationContext.getSystemService<BluetoothManager>() ?: return false\n            val adapter = bm.adapter\n            if (adapter != null && adapter.isEnabled) {\n                val pairedDevices = adapter.bondedDevices\n                return pairedDevices.any { device ->\n                    bm.getConnectionState(\n                        device, BluetoothProfile.STATE_CONNECTED\n                    ) == BluetoothProfile.STATE_CONNECTED\n                }\n            }\n            return false\n        }\n\n    val isHeadsetConnected: Boolean\n        @SuppressLint(\"ObsoleteSdkInt\") get() {\n            val am = applicationContext.getSystemService<AudioManager>() ?: return false\n            val devices = am.getDevices(AudioManager.GET_DEVICES_OUTPUTS)\n            return devices.any { device ->\n                device.type == AudioDeviceInfo.TYPE_WIRED_HEADSET\n                        || device.type == AudioDeviceInfo.TYPE_WIRED_HEADPHONES\n                        || device.type == AudioDeviceInfo.TYPE_BLUETOOTH_A2DP\n                        || device.type == AudioDeviceInfo.TYPE_BLUETOOTH_SCO\n                        || (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O && device.type == AudioDeviceInfo.TYPE_USB_HEADSET)\n                        || (Build.VERSION.SDK_INT >= Build.VERSION_CODES.S && device.type == AudioDeviceInfo.TYPE_BLE_HEADSET)\n            }\n        }\n}\n"
  },
  {
    "path": "yenaly_libs/src/main/java/com/yenaly/yenaly_libs/utils/FileUtil.kt",
    "content": "@file:JvmName(\"FileUtil\")\n\npackage com.yenaly.yenaly_libs.utils\n\nimport androidx.annotation.WorkerThread\nimport java.io.File\nimport java.io.InputStream\nimport java.io.OutputStream\nimport java.math.BigInteger\nimport java.security.MessageDigest\n\n@WorkerThread\nfun InputStream.copyTo(\n    out: OutputStream,\n    bufferSize: Int = DEFAULT_BUFFER_SIZE,\n    progress: (Long) -> Unit\n): Long {\n    var bytesCopied: Long = 0\n    val buffer = ByteArray(bufferSize)\n    var bytes = read(buffer)\n    while (bytes >= 0) {\n        out.write(buffer, 0, bytes)\n        bytesCopied += bytes\n        progress.invoke(bytesCopied)\n        bytes = read(buffer)\n    }\n    return bytesCopied\n}\n\nval File?.folderSize: Long\n    get() {\n        var size = 0L\n        val files = this?.listFiles()\n        files?.forEach { file -> size += if (file.isDirectory) file.folderSize else file.length() }\n        return size\n    }\n\n/**\n * 创建文件夹并在文件夹内创建.nomedia文件\n *\n * 为了防止媒体库扫描到文件夹内的文件\n */\nfun File.makeFolderNoMedia() {\n    if (!exists()) {\n        mkdirs()\n    } else if (!isDirectory) {\n        return\n    }\n    val noMedia = File(this, \".nomedia\")\n    if (!noMedia.exists()) {\n        noMedia.createNewFile()\n    }\n}\n\nfun File.createFileIfNotExists(): Boolean {\n    return if (!exists()) {\n        parentFile?.mkdirs()\n        createNewFile()\n    } else {\n        isFile\n    }\n}\n\nfun File.createDirIfNotExists(): Boolean {\n    return if (!exists()) mkdirs() else isDirectory\n}\n\n@WorkerThread\nfun File.md5(): String {\n    val md = MessageDigest.getInstance(\"MD5\")\n    return BigInteger(1, md.digest(readBytes())).toString(16).padStart(32, '0')\n}"
  },
  {
    "path": "yenaly_libs/src/main/java/com/yenaly/yenaly_libs/utils/ImageUtil.kt",
    "content": "@file:JvmName(\"ImageUtil\")\n\npackage com.yenaly.yenaly_libs.utils\n\nimport android.graphics.Bitmap\nimport android.graphics.drawable.Drawable\nimport androidx.core.graphics.drawable.toBitmapOrNull\nimport java.io.ByteArrayOutputStream\nimport java.io.File\n\nfun Drawable.toByteArrayOrNull(): ByteArray? {\n    return toBitmapOrNull()?.run {\n        ByteArrayOutputStream().use { stream ->\n            compress(Bitmap.CompressFormat.PNG, 100, stream)\n            stream.toByteArray()\n        }\n    }\n}\n\nfun Bitmap.saveTo(\n    file: File,\n    format: Bitmap.CompressFormat = Bitmap.CompressFormat.PNG,\n    quality: Int = 100\n): Boolean {\n    return try {\n        file.outputStream().buffered().use { stream ->\n            compress(format, quality, stream)\n            true\n        }\n    } catch (e: Exception) {\n        e.printStackTrace()\n        false\n    }\n}\n\nfun Drawable.saveTo(\n    file: File,\n    format: Bitmap.CompressFormat = Bitmap.CompressFormat.PNG,\n    quality: Int = 100\n): Boolean {\n    return toBitmapOrNull()?.saveTo(file, format, quality) == true\n}"
  },
  {
    "path": "yenaly_libs/src/main/java/com/yenaly/yenaly_libs/utils/LanguageHelper.kt",
    "content": "package com.yenaly.yenaly_libs.utils\n\nimport android.annotation.SuppressLint\nimport android.os.Build\nimport android.os.LocaleList\nimport java.util.Locale\n\n@SuppressLint(\"ObsoleteSdkInt\")\nobject LanguageHelper {\n\n    val preferredLanguage: Locale\n        get() = if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.N) {\n            LocaleList.getDefault()[0]\n        } else {\n            Locale.getDefault()\n        }\n}"
  },
  {
    "path": "yenaly_libs/src/main/java/com/yenaly/yenaly_libs/utils/LogUtil.kt",
    "content": "package com.yenaly.yenaly_libs.utils\n\nimport android.util.Log\nimport com.yenaly.yenaly_libs.BuildConfig\n\nfun <T : Any> logFieldsChange(tag: String, oldItem: T, newItem: T) {\n    if (!BuildConfig.DEBUG) return\n\n    val oldFields = oldItem::class.java.declaredFields\n    val newFields = newItem::class.java.declaredFields\n\n    for (i in oldFields.indices) {\n        oldFields[i].isAccessible = true\n        newFields[i].isAccessible = true\n        val oldValue = oldFields[i].get(oldItem)\n        val newValue = newFields[i].get(newItem)\n        if (oldValue != newValue) {\n            Log.i(tag, \"Field ${oldFields[i].name} changed from $oldValue to $newValue\")\n        }\n    }\n}"
  },
  {
    "path": "yenaly_libs/src/main/java/com/yenaly/yenaly_libs/utils/OrientationManager.kt",
    "content": "package com.yenaly.yenaly_libs.utils\n\nimport android.provider.Settings\nimport android.view.OrientationEventListener\nimport androidx.lifecycle.Lifecycle\nimport androidx.lifecycle.LifecycleEventObserver\nimport androidx.lifecycle.LifecycleOwner\n\n\n/**\n * @project Han1meViewer\n * @author Yenaly Liew\n * @time 2024/04/10 010 20:49\n */\nclass OrientationManager(private var orientationChangeListener: OrientationChangeListener? = null) :\n    OrientationEventListener(applicationContext), LifecycleEventObserver {\n\n    private var screenOrientation: ScreenOrientation = ScreenOrientation.PORTRAIT\n\n    enum class ScreenOrientation {\n        LANDSCAPE, REVERSED_LANDSCAPE,\n        PORTRAIT, REVERSED_PORTRAIT;\n\n        val isPortrait get() = this == PORTRAIT || this == REVERSED_PORTRAIT\n        val isLandscape get() = this == LANDSCAPE || this == REVERSED_LANDSCAPE\n    }\n\n    override fun onOrientationChanged(orientation: Int) {\n        if (orientation == -1) {\n            return\n        }\n        try {\n            val isRotateEnabled = Settings.System.getInt(\n                applicationContext.contentResolver,\n                Settings.System.ACCELEROMETER_ROTATION\n            )\n            if (isRotateEnabled == 0) return\n        } catch (e: Settings.SettingNotFoundException) {\n            e.printStackTrace()\n        }\n        val newOrientation = when (orientation) {\n            in 60..140 -> ScreenOrientation.REVERSED_LANDSCAPE\n            in 140..220 -> ScreenOrientation.REVERSED_PORTRAIT\n            in 220..300 -> ScreenOrientation.LANDSCAPE\n            else -> ScreenOrientation.PORTRAIT\n        }\n        if (newOrientation !== screenOrientation) {\n            screenOrientation = newOrientation\n            orientationChangeListener?.onOrientationChanged(screenOrientation)\n        }\n    }\n\n    fun interface OrientationChangeListener {\n        fun onOrientationChanged(orientation: ScreenOrientation)\n    }\n\n    override fun onStateChanged(source: LifecycleOwner, event: Lifecycle.Event) {\n        when (event) {\n            Lifecycle.Event.ON_START -> enable()\n            Lifecycle.Event.ON_STOP -> disable()\n            else -> Unit\n        }\n    }\n}"
  },
  {
    "path": "yenaly_libs/src/main/java/com/yenaly/yenaly_libs/utils/ResourceUtil.kt",
    "content": "@file:Suppress(\"unused\")\n@file:JvmName(\"ResourceUtil\")\n\npackage com.yenaly.yenaly_libs.utils\n\nimport android.annotation.SuppressLint\nimport android.content.res.Configuration\nimport android.content.res.Resources\nimport androidx.core.util.TypedValueCompat\n\nval Number.dpF: Float\n    @JvmName(\"dpToPxF\")\n    get() = TypedValueCompat.dpToPx(\n        this.toFloat(),\n        applicationContext.resources.displayMetrics\n    )\n\nval Number.spF: Float\n    @JvmName(\"spToPxF\")\n    get() = TypedValueCompat.spToPx(\n        this.toFloat(),\n        applicationContext.resources.displayMetrics\n    )\n\n/**\n * 通过dp获取相应px值\n */\nval Number.dp: Int\n    @JvmName(\"dpToPx\")\n    get() {\n        val f = dpF\n        val res = (if (f >= 0) f + 0.5f else f - 0.5f).toInt()\n        return res\n    }\n\n/**\n * 通过sp获取相应px值\n */\nval Number.sp: Int\n    @JvmName(\"spToPx\")\n    get() {\n        val f = spF\n        val res = (if (f >= 0) f + 0.5f else f - 0.5f).toInt()\n        return res\n    }\n\n/**\n * 获取本地储存状态栏高度px\n */\nval statusBarHeight: Int\n    @SuppressLint(\"DiscouragedApi\", \"InternalInsetResource\")\n    get() {\n        val resources: Resources = applicationContext.resources\n        val resourceId = resources.getIdentifier(\"status_bar_height\", \"dimen\", \"android\")\n        return resources.getDimensionPixelSize(resourceId)\n    }\n\n/**\n * 获取本地储存导航栏高度px\n */\nval navBarHeight: Int\n    @SuppressLint(\"DiscouragedApi\", \"InternalInsetResource\")\n    get() {\n        val resources: Resources = applicationContext.resources\n        val resourceId = resources.getIdentifier(\"navigation_bar_height\", \"dimen\", \"android\")\n        return resources.getDimensionPixelSize(resourceId)\n    }\n\n/**\n * 判断当前是否横屏\n */\nval isOrientationLandscape: Boolean\n    get() {\n        return applicationContext.resources.configuration.orientation == Configuration.ORIENTATION_LANDSCAPE\n    }"
  },
  {
    "path": "yenaly_libs/src/main/java/com/yenaly/yenaly_libs/utils/ShareUtil.kt",
    "content": "@file:JvmName(\"ShareUtil\")\n@file:Suppress(\"unused\")\n\npackage com.yenaly.yenaly_libs.utils\n\nimport android.net.Uri\nimport androidx.core.app.ShareCompat\nimport com.yenaly.yenaly_libs.ActivityManager\n\n@JvmOverloads\nfun shareText(content: CharSequence, title: CharSequence? = null) {\n    share(\"text/plain\") {\n        setText(content)\n        setChooserTitle(title)\n    }\n}\n\n@JvmOverloads\nfun shareImages(imageUris: List<Uri>, title: CharSequence? = null) {\n    shareTextAndImages(content = null, imageUri = imageUris, title = title)\n}\n\n@JvmOverloads\nfun shareTextAndImages(content: CharSequence?, imageUri: List<Uri>, title: CharSequence? = null) {\n    share(\"image/*\") {\n        setText(content)\n        imageUri.forEach(::addStream)\n        setChooserTitle(title)\n    }\n}\n\n@JvmOverloads\nfun shareFiles(uris: List<Uri>, title: CharSequence? = null, mimeType: String? = null) {\n    share(mimeType ?: uris.firstOrNull()?.mimeType) {\n        uris.forEach(::addStream)\n        setChooserTitle(title)\n    }\n}\n\ninline fun share(mimeType: String?, crossinline block: ShareCompat.IntentBuilder.() -> Unit) =\n    ShareCompat\n        .IntentBuilder(ActivityManager.currentActivity.get() ?: applicationContext)\n        .setType(mimeType)\n        .apply(block)\n        .startChooser()"
  },
  {
    "path": "yenaly_libs/src/main/java/com/yenaly/yenaly_libs/utils/SharedPreferencesUtil.kt",
    "content": "@file:JvmName(\"SharedPreferencesUtil\")\n@file:Suppress(\"UNCHECKED_CAST\", \"unused\")\n\npackage com.yenaly.yenaly_libs.utils\n\nimport android.content.Context\nimport android.content.SharedPreferences\nimport androidx.core.content.edit\n\nimport java.io.ByteArrayInputStream\nimport java.io.ByteArrayOutputStream\nimport java.io.ObjectInputStream\nimport java.io.ObjectOutputStream\nimport java.net.URLDecoder\nimport java.net.URLEncoder\n\n/**\n * 创建SharedPreferences\n *\n * @param name sp名称\n * @param mode 模式\n */\nprivate fun Context.sp(\n    name: String = packageName,\n    mode: Int = Context.MODE_PRIVATE\n): SharedPreferences {\n    return getSharedPreferences(name, mode)\n}\n\n/**\n * 把值存入SharedPreferences内\n *\n * @param Ace   泛型\n * @param key   储存键\n * @param value 储存值\n * @param name  sp名称\n */\n@JvmOverloads\nfun <Ace> putSpValue(\n    key: String,\n    value: Ace,\n    name: String = applicationContext.packageName\n) {\n    applicationContext.sp(name = name).edit {\n        when (value) {\n            is Long -> putLong(key, value)\n            is String -> putString(key, value)\n            is Int -> putInt(key, value)\n            is Boolean -> putBoolean(key, value)\n            is Float -> putFloat(key, value)\n            else -> putString(key, serialize(value))\n        }\n    }\n}\n\n/**\n * 把值从SharedPreferences取出\n *\n * @param Taffy   泛型\n * @param key     储存键\n * @param default 缺省值\n * @param name    sp名称\n *\n * @return 储存值\n */\n@JvmOverloads\nfun <Taffy> getSpValue(\n    key: String,\n    default: Taffy,\n    name: String = applicationContext.packageName\n): Taffy {\n    return applicationContext.sp(name = name).run {\n        val result = when (default) {\n            is Long -> getLong(key, default)\n            is String -> getString(key, default)\n            is Int -> getInt(key, default)\n            is Boolean -> getBoolean(key, default)\n            is Float -> getFloat(key, default)\n            else -> deSerialization(getString(key, serialize(default)))\n        }\n        result as Taffy\n    }\n}\n\n/**\n * 通过委托方式懒加载获取sp值\n *\n * @param Taffy   泛型\n * @param key     储存键\n * @param default 缺省值\n * @param name    sp名称\n */\n@JvmOverloads\nfun <Taffy> spValue(\n    key: String,\n    default: Taffy,\n    name: String = applicationContext.packageName\n) =\n    lazy(LazyThreadSafetyMode.NONE) {\n        getSpValue(key, default, name)\n    }\n\n/**\n * 删除sp内特定值\n *\n * @param key  储存键\n * @param name sp名称\n */\n@JvmOverloads\nfun removeSpValue(\n    key: String,\n    name: String = applicationContext.packageName\n) {\n    applicationContext.sp(name = name).edit { remove(key) }\n}\n\n/**\n * 清除sp的所有内容\n *\n * @param name sp名称\n */\n@JvmOverloads\nfun clearSharedPreferences(\n    name: String = applicationContext.packageName\n) {\n    applicationContext.sp(name = name).edit { clear() }\n}\n\n/**\n * 序列化\n */\nprivate fun <Nyaru> serialize(obj: Nyaru): String {\n    val byteArrayOutputStream = ByteArrayOutputStream()\n    val objectOutputStream = ObjectOutputStream(byteArrayOutputStream)\n    objectOutputStream.writeObject(obj)\n    var serStr = byteArrayOutputStream.toString(\"ISO-8859-1\")\n    serStr = URLEncoder.encode(serStr, \"UTF-8\")\n    objectOutputStream.close()\n    byteArrayOutputStream.close()\n    return serStr\n}\n\n/**\n * 反序列化\n */\nprivate fun <Bekki> deSerialization(str: String?): Bekki {\n    val redStr = URLDecoder.decode(str, \"UTF-8\")\n    val byteArrayInputStream = ByteArrayInputStream(redStr.toByteArray(charset(\"ISO-8859-1\")))\n    val objectInputStream = ObjectInputStream(byteArrayInputStream)\n    val obj = objectInputStream.readObject() as Bekki\n    objectInputStream.close()\n    byteArrayInputStream.close()\n    return obj\n}"
  },
  {
    "path": "yenaly_libs/src/main/java/com/yenaly/yenaly_libs/utils/SingleFlowLaunch.kt",
    "content": "package com.yenaly.yenaly_libs.utils\n\nimport kotlinx.coroutines.CoroutineScope\nimport kotlinx.coroutines.CoroutineStart\nimport kotlinx.coroutines.Job\nimport kotlinx.coroutines.launch\nimport java.util.concurrent.atomic.AtomicInteger\nimport kotlin.coroutines.CoroutineContext\nimport kotlin.coroutines.EmptyCoroutineContext\n\n/**\n * For ViewModelScope.\n * Avoid triggering request multiple times when activity recreates and\n * you call `viewModelScope.launch` function in activity or fragment.\n *\n * For example:\n * ```kotlin\n * val singleFlowLaunch = SingleFlowLaunch()\n *\n * fun foo(param: Any) {\n *     singleFlowLaunch.singleLaunch(viewModelScope, UNIQUE_TAG) {\n *         Repository.suspendFunction(param)\n *     }\n * }\n * ```\n *\n * @author Yenaly Liew\n * @time 2022/07/17 017 22:34\n */\n@Deprecated(\"totally trash\")\nclass SingleFlowLaunch {\n\n    private val jobMap = mutableMapOf<SuspendCoroutineScopeBlock, AtomicInteger>()\n\n    /**\n     * Single [CoroutineScope.launch] only for ViewModelScope,\n     * avoid triggering request multiple times when activity recreates and\n     * you call `viewModelScope.launch` function in activity or fragment.\n     *\n     * If you use [SharedFlow][kotlinx.coroutines.flow.SharedFlow] with [singleLaunch],\n     * you should set the param `replay` to 1 or higher to cache the latest data.\n     * Otherwise, you might not get the data.\n     */\n    fun singleLaunch(\n        viewModelScope: CoroutineScope,\n        context: CoroutineContext = EmptyCoroutineContext,\n        start: CoroutineStart = CoroutineStart.DEFAULT,\n        block: SuspendCoroutineScopeBlock,\n    ): Job? {\n        if (jobMap[block] == null) {\n            jobMap[block] = AtomicInteger(0)\n        }\n        jobMap[block]!!.let { int ->\n            if (int.getAndIncrement() != 0) {\n                return null\n            } else {\n                return viewModelScope.launch(context, start, block)\n            }\n        }\n    }\n}\n\ntypealias SuspendCoroutineScopeBlock = suspend CoroutineScope.() -> Unit"
  },
  {
    "path": "yenaly_libs/src/main/java/com/yenaly/yenaly_libs/utils/SnackBarUtil.kt",
    "content": "@file:JvmName(\"SnackBarUtil\")\n\npackage com.yenaly.yenaly_libs.utils\n\nimport android.app.Activity\nimport android.view.View\nimport androidx.annotation.StringRes\nimport androidx.fragment.app.Fragment\nimport com.google.android.material.snackbar.Snackbar\n\n@JvmOverloads\ninline fun Activity.showSnackBar(\n    message: CharSequence,\n    length: Int = Snackbar.LENGTH_SHORT,\n    view: View = findViewById(android.R.id.content),\n    action: Snackbar.() -> Unit = {}\n) {\n    Snackbar.make(view, message, length).apply(action).show()\n}\n\n@JvmOverloads\ninline fun Activity.showSnackBar(\n    @StringRes message: Int,\n    length: Int = Snackbar.LENGTH_SHORT,\n    view: View = findViewById(android.R.id.content),\n    action: Snackbar.() -> Unit = {}\n) {\n    Snackbar.make(view, message, length).apply(action).show()\n}\n\n@JvmOverloads\ninline fun Fragment.showSnackBar(\n    message: CharSequence,\n    length: Int = Snackbar.LENGTH_SHORT,\n    view: View = requireView(),\n    action: Snackbar.() -> Unit = {}\n) {\n    Snackbar.make(view, message, length).apply(action).show()\n}\n\n@JvmOverloads\ninline fun Fragment.showSnackBar(\n    @StringRes message: Int,\n    length: Int = Snackbar.LENGTH_SHORT,\n    view: View = requireView(),\n    action: Snackbar.() -> Unit = {}\n) {\n    Snackbar.make(view, message, length).apply(action).show()\n}\n\n\n"
  },
  {
    "path": "yenaly_libs/src/main/java/com/yenaly/yenaly_libs/utils/SystemStatusUtil.kt",
    "content": "@file:Suppress(\"unused\")\n\npackage com.yenaly.yenaly_libs.utils\n\nimport android.content.res.Configuration\nimport android.view.Window\nimport android.view.WindowManager\nimport androidx.annotation.ColorInt\nimport androidx.core.view.ViewCompat\nimport androidx.core.view.WindowCompat\nimport androidx.core.view.WindowInsetsCompat\n\n/**\n * 取消当前透明状态栏，再加一个带颜色的状态栏\n *\n * @param color 想要的状态栏颜色\n */\n@Suppress(\"DEPRECATION\")\nfun Window.addStatusBarWithColor(@ColorInt color: Int) {\n    //取消设置透明状态栏,使 ContentView 内容不再覆盖状态栏\n    clearFlags(WindowManager.LayoutParams.FLAG_TRANSLUCENT_STATUS)\n    //需要设置这个 flag 才能调用 setStatusBarColor 来设置状态栏颜色\n    addFlags(WindowManager.LayoutParams.FLAG_DRAWS_SYSTEM_BAR_BACKGROUNDS)\n    statusBarColor = color\n}\n\n/**\n * 获取当前实际状态栏高度\n */\nval Window.currentStatusBarHeight: Int\n    get() {\n        val windowInsetsCompat = ViewCompat.getRootWindowInsets(decorView)\n        return windowInsetsCompat?.getInsets(WindowInsetsCompat.Type.statusBars())?.top\n            ?: statusBarHeight\n    }\n\n/**\n * 获取当前实际导航栏高度\n */\nval Window.currentNavBarHeight: Int\n    get() {\n        val windowInsetsCompat = ViewCompat.getRootWindowInsets(decorView)\n        return windowInsetsCompat?.getInsets(WindowInsetsCompat.Type.navigationBars())?.bottom\n            ?: navBarHeight\n    }\n\n/**\n * 当前状态栏是否可见\n */\nval Window.isStatusBarVisible: Boolean\n    get() {\n        val windowInsetsCompat = ViewCompat.getRootWindowInsets(decorView)\n        return windowInsetsCompat?.isVisible(WindowInsetsCompat.Type.statusBars()) ?: true\n    }\n\n/**\n * 当前导航栏是否可见\n */\nval Window.isNavBarVisible: Boolean\n    get() {\n        val windowInsetsCompat = ViewCompat.getRootWindowInsets(decorView)\n        return windowInsetsCompat?.isVisible(WindowInsetsCompat.Type.navigationBars()) ?: true\n    }\n\n/**\n * 控制状态栏是否可见\n */\nfun Window.controlStatusBar(isVisible: Boolean) {\n    val controller = WindowCompat.getInsetsController(this, decorView)\n    if (isVisible) {\n        controller.show(WindowInsetsCompat.Type.statusBars())\n    } else {\n        controller.hide(WindowInsetsCompat.Type.statusBars())\n    }\n}\n\n/**\n * 控制导航栏是否可见\n */\nfun Window.controlNavBar(isVisible: Boolean) {\n    val controller = WindowCompat.getInsetsController(this, decorView)\n    if (isVisible) {\n        controller.show(WindowInsetsCompat.Type.navigationBars())\n    } else {\n        controller.hide(WindowInsetsCompat.Type.navigationBars())\n    }\n}\n\n/**\n * 控制系统栏（包含状态栏，导航栏）是否可见\n */\nfun Window.controlSystemBars(isVisible: Boolean) {\n    val controller = WindowCompat.getInsetsController(this, decorView)\n    if (isVisible) {\n        controller.show(WindowInsetsCompat.Type.systemBars())\n    } else {\n        controller.hide(WindowInsetsCompat.Type.systemBars())\n    }\n}\n\n/**\n * 是否将系统栏图标切换成亮色模式\n *\n * @param statusBar 设置状态栏图标为亮色模式\n * @param navBar    设置导航栏图标为亮色模式\n */\nfun Window.setSystemBarIconLightMode(statusBar: Boolean, navBar: Boolean = false) {\n    val controller = WindowCompat.getInsetsController(this, decorView)\n    controller.isAppearanceLightStatusBars = statusBar\n    controller.isAppearanceLightNavigationBars = navBar\n}\n\n/**\n * 当前软键盘是否可见\n *\n * @return 是否可见\n */\ninline val Window.isImeVisible: Boolean\n    get() {\n        val windowInsetsCompat = ViewCompat.getRootWindowInsets(decorView)\n        return windowInsetsCompat?.isVisible(WindowInsetsCompat.Type.ime()) ?: false\n    }\n\n/**\n * 是否显示软键盘\n *\n * @param ime    是否显示软键盘\n */\nfun Window.showIme(ime: Boolean) {\n    val controller = WindowCompat.getInsetsController(this, decorView)\n    if (ime) {\n        controller.show(WindowInsetsCompat.Type.ime())\n    } else {\n        controller.hide(WindowInsetsCompat.Type.ime())\n    }\n}\n\n/**\n * 当前是否为夜间模式\n */\nval isAppDarkMode: Boolean\n    get() {\n        return applicationContext.resources.configuration.uiMode and Configuration.UI_MODE_NIGHT_MASK == Configuration.UI_MODE_NIGHT_YES\n    }\n\n/**\n * Sets whether the decor view should fit root-level content views for\n * {@link WindowInsetsCompat}.\n * <p>\n * If set to {@code false}, the framework will not fit the content view to the insets and will\n * just pass through the {@link WindowInsetsCompat} to the content view.\n * </p>\n * <p>\n * Please note: using the {@link View#setSystemUiVisibility(int)} API in your app can\n * conflict with this method. Please discontinue use of {@link View#setSystemUiVisibility(int)}.\n * </p>\n *\n * @param decorFitsSystemWindows Whether the decor view should fit root-level content views for\n *                               insets.\n */\nfun Window.isDecorFitsSystemWindows(decorFitsSystemWindows: Boolean) =\n    WindowCompat.setDecorFitsSystemWindows(this, decorFitsSystemWindows)"
  },
  {
    "path": "yenaly_libs/src/main/java/com/yenaly/yenaly_libs/utils/TextUtil.kt",
    "content": "@file:JvmName(\"TextUtil\")\n\npackage com.yenaly.yenaly_libs.utils\n\nimport android.text.format.Formatter\nimport androidx.annotation.IntRange\nimport java.util.*\n\n/**\n * 把一个秒数转化为时间格式，常用于视频持续时间。\n *\n * 例如：123(s) -> 02:03\n *\n * @author Yenaly Liew\n */\nfun Long.secondToTimeCase(): String {\n    val second: Long = this % 60\n    var minute: Long = this / 60\n    var hour = 0L\n    if (minute >= 60) {\n        hour = minute / 60\n        minute %= 60\n    }\n    val secondString = if (second < 10) \"0$second\" else second.toString()\n    val minuteString = if (minute < 10) \"0$minute\" else minute.toString()\n    val hourString = if (hour < 10) \"0$hour\" else hour.toString()\n    return if (hour != 0L) \"$hourString:$minuteString:$secondString\" else \"$minuteString:$secondString\"\n}\n\n/**\n * 把一个数转化为带万、亿的格式，常用于视频播放量等。\n *\n * 例如：102002 -> 10.2万\n *\n * @author Yenaly Liew\n */\nfun Long.formatPlayCount(): String {\n    return when {\n        this < 0 -> \"0\"\n        this < 1_0000 -> this.toString()\n        this < 1_0000_0000 -> String.format(\n            Locale.getDefault(),\n            \"%d.%02d万\",\n            this / 1_0000,\n            this % 1_0000 / 100\n        )\n\n        else -> String.format(\n            Locale.getDefault(),\n            \"%d.%02d亿\",\n            this / 1_0000_0000,\n            this % 1_0000_0000 / 100_0000\n        )\n    }\n}\n\n@Deprecated(\"Use formatFileSizeV2 instead.\", ReplaceWith(\"formatFileSizeV2()\"))\nfun Long.formatFileSize(short: Boolean = true): String {\n    if (short) {\n        return Formatter.formatShortFileSize(applicationContext, this)\n    }\n    return Formatter.formatFileSize(applicationContext, this)\n}\n\nprivate val SI_UNITS = arrayOf(\"B\", \"kB\", \"MB\", \"GB\", \"TB\")\nprivate val IEC_UNITS = arrayOf(\"B\", \"KiB\", \"MiB\", \"GiB\", \"TiB\")\n\n/**\n * 把一个数转化为带单位的文件大小格式，常用于文件大小等。\n *\n * 默认使用二进制单位（1024），可以通过 useSi 参数切换为十进制单位（1000）。\n *\n * @param useSi 是否使用十进制单位（1000），默认使用二进制单位（1024）。\n * @param decimalPlaces 小数点位数，默认为 1。\n * @param stripTrailingZeros 如果能整除，是否去除小数点后的 0，默认去除。\n */\nfun Long.formatFileSizeV2(\n    useSi: Boolean = false,\n    @IntRange(from = 0) decimalPlaces: Int = 1,\n    stripTrailingZeros: Boolean = true,\n): String {\n    val unit = if (useSi) 1000 else 1024\n    if (this < unit) return \"$this B\"\n\n    val units = if (useSi) SI_UNITS else IEC_UNITS\n\n    var value = this.toDouble()\n    var unitIndex = 0\n\n    while (value >= unit && unitIndex < units.size - 1) {\n        value /= unit\n        unitIndex++\n    }\n\n    return if (decimalPlaces == 0 || (stripTrailingZeros && value % 1 == 0.0)) {\n        \"%.0f %s\".format(Locale.getDefault(), value, units[unitIndex])\n    } else {\n        \"%.${decimalPlaces}f %s\".format(Locale.getDefault(), value, units[unitIndex])\n    }\n}\n\n/**\n * 把一个数转化为带单位的速度格式，常用于下载速度等。\n *\n * 默认使用二进制单位（1024），可以通过 useSi 参数切换为十进制单位（1000）。\n *\n * @param useSi 是否使用十进制单位（1000），默认使用二进制单位（1024）。\n * @param decimalPlaces 小数点位数，默认为 1。\n * @param stripTrailingZeros 如果能整除，是否去除小数点后的 0，默认去除。\n */\nfun Long.formatBytesPerSecond(\n    useSi: Boolean = false,\n    @IntRange(from = 0) decimalPlaces: Int = 1,\n    stripTrailingZeros: Boolean = true,\n): String {\n    return formatFileSizeV2(useSi, decimalPlaces, stripTrailingZeros) + \"/s\"\n}"
  },
  {
    "path": "yenaly_libs/src/main/java/com/yenaly/yenaly_libs/utils/ToastUtil.kt",
    "content": "@file:JvmName(\"ToastUtil\")\n\npackage com.yenaly.yenaly_libs.utils\n\nimport android.widget.Toast\nimport androidx.annotation.StringRes\n\nfun showShortToast(text: String?) {\n    Toast.makeText(applicationContext, \"$text\", Toast.LENGTH_SHORT).show()\n}\n\nfun showLongToast(text: String?) {\n    Toast.makeText(applicationContext, \"$text\", Toast.LENGTH_LONG).show()\n}\n\nfun showShortToast(@StringRes text: Int) {\n    Toast.makeText(\n        applicationContext,\n        applicationContext.getString(text),\n        Toast.LENGTH_SHORT\n    ).show()\n}\n\nfun showLongToast(@StringRes text: Int) {\n    Toast.makeText(\n        applicationContext,\n        applicationContext.getString(text),\n        Toast.LENGTH_LONG\n    ).show()\n}"
  },
  {
    "path": "yenaly_libs/src/main/java/com/yenaly/yenaly_libs/utils/UnicodeUtil.kt",
    "content": "@file:JvmName(\"UnicodeUtil\")\n\npackage com.yenaly.yenaly_libs.utils\n\n/**\n * 将字符串转成Unicode编码，包括但不限于中文\n *\n * @param src 原始字符串，包括但不限于中文\n * @return Unicode编码字符串\n */\n@JvmName(\"decode\")\nfun stringDecodeToUnicode(src: String): String {\n    val builder = StringBuilder()\n    for (element in src) {\n        // 如果你的Kotlin版本低于1.5，这里 element.code 会报错 找不到方法,请替换成:\n        // Kotlin < 1.5\n        // var s = Integer.toHexString(element.toInt())\n        // Kotlin >= 1.5\n        var s = Integer.toHexString(element.code)\n\n        if (s.length == 2) {// 英文转16进制后只有两位，补全4位\n            s = \"00$s\"\n        }\n        builder.append(\"\\\\u$s\")\n    }\n    return builder.toString()\n}\n\n/**\n * 解码Unicode字符串，得到原始字符串\n *\n * @param unicode Unicode字符串\n * @return 解码后的原始字符串\n */\n@JvmName(\"encode\")\nfun unicodeEncodeToString(unicode: String): String {\n    val builder = StringBuilder()\n    val hex = unicode.split(\"\\\\\\\\u\".toRegex()).toTypedArray()\n    for (i in 1 until hex.size) {\n        val data = hex[i].toInt(16)\n        builder.append(data.toChar())\n    }\n    return builder.toString()\n}"
  },
  {
    "path": "yenaly_libs/src/main/java/com/yenaly/yenaly_libs/utils/UriUtil.kt",
    "content": "@file:JvmName(\"UriUtil\")\n\npackage com.yenaly.yenaly_libs.utils\n\nimport android.net.Uri\nimport android.webkit.MimeTypeMap\n\ninline val Uri.fileExtension get() = MimeTypeMap.getSingleton().getExtensionFromMimeType(mimeType)\n\ninline val Uri.mimeType get() = application.contentResolver.getType(this)"
  },
  {
    "path": "yenaly_libs/src/main/java/com/yenaly/yenaly_libs/utils/_AesUtil.kt",
    "content": "@file:Suppress(\"unused\")\n@file:JvmName(\"AesUtil\")\n\npackage com.yenaly.yenaly_libs.utils\n\nimport java.io.File\nimport java.io.FileInputStream\nimport java.io.FileOutputStream\nimport java.security.Key\nimport java.util.Locale\nimport javax.crypto.Cipher\nimport javax.crypto.CipherInputStream\nimport javax.crypto.KeyGenerator\nimport javax.crypto.spec.IvParameterSpec\nimport javax.crypto.spec.SecretKeySpec\n\n/**\n * Created by luyao\n * on 2019/7/1 16:09\n */\n\nprivate const val KEY_ALGORITHM = \"AES\"\nprivate const val CIPHER_ALGORITHM_DEFAULT = \"AES\"\nconst val AES_CFB_NOPADDING = \"AES/CFB/NoPadding\"\nconst val AES_ECB_NOPADDING = \"AES/ECB/NoPadding\"\n\n/**\n * Aes encrypt byte array\n * @param key the encryption key\n * @param iv the IV (CFB,CBC,CTR need IV)\n * @param algorithm the algorithm parameters\n */\nfun ByteArray.aesEncrypt(\n    key: ByteArray,\n    iv: ByteArray = ByteArray(16),\n    algorithm: String = AES_CFB_NOPADDING\n): ByteArray {\n    val cipher = initCipher(Cipher.ENCRYPT_MODE, key, iv, algorithm)\n    return cipher.doFinal(this)\n}\n\n/**\n * Aes decrypt byte array\n * @param key the decryption key\n * @param iv the IV (CFB,CBC,CTR need IV)\n * @param algorithm the algorithm parameters\n */\nfun ByteArray.aesDecrypt(\n    key: ByteArray,\n    iv: ByteArray = ByteArray(16),\n    algorithm: String = AES_CFB_NOPADDING\n): ByteArray {\n    val cipher = initCipher(Cipher.DECRYPT_MODE, key, iv, algorithm)\n    return cipher.doFinal(this)\n}\n\n/**\n * Aes encrypt file\n * @param key the encryption key\n * @param iv the IV (CFB,CBC,CTR need IV)\n * @param destFilePath dest encrypted file\n * @param algorithm the algorithm parameters\n */\nfun File.aesEncrypt(\n    key: ByteArray,\n    iv: ByteArray,\n    destFilePath: String,\n    algorithm: String = AES_CFB_NOPADDING\n): File? {\n    return handleFile(Cipher.ENCRYPT_MODE, key, iv, algorithm, path, destFilePath)\n}\n\n/**\n * Aes decrypt file\n * @param key the decryption key\n * @param iv the IV (CFB,CBC,CTR need IV)\n * @param destFilePath dest decrypted file\n * @param algorithm the algorithm parameters\n */\nfun File.aesDecrypt(\n    key: ByteArray,\n    iv: ByteArray,\n    destFilePath: String,\n    algorithm: String = AES_CFB_NOPADDING\n): File? {\n    return handleFile(Cipher.DECRYPT_MODE, key, iv, algorithm, path, destFilePath)\n}\n\n/**\n * Generate aes key byte array , default size is 128\n */\nfun initAESKey(size: Int = 128): ByteArray {\n    val kg = KeyGenerator.getInstance(KEY_ALGORITHM)\n    kg.init(size)\n    return kg.generateKey().encoded\n}\n\nprivate fun toKey(key: ByteArray): Key = SecretKeySpec(key, KEY_ALGORITHM)\n\n/**\n * Init Cipher\n * @param mode the operation mode of this cipher\n * @param key the encrypt/decrypt key\n * @param iv the IV\n * @param algorithm the algorithm parameters\n */\nfun initCipher(\n    mode: Int,\n    key: ByteArray,\n    iv: ByteArray = ByteArray(16),\n    algorithm: String\n): Cipher {\n    val k = toKey(key)\n    val cipher = Cipher.getInstance(algorithm)\n    val cipherAlgorithm = algorithm.uppercase(Locale.getDefault())\n    if (cipherAlgorithm.contains(\"CFB\") || cipherAlgorithm.contains(\"CBC\")\n        || cipherAlgorithm.contains(\"CTR\")\n    )\n        cipher.init(mode, k, IvParameterSpec(iv))\n    else\n        cipher.init(mode, k)\n    return cipher\n}\n\nprivate fun handleFile(\n    mode: Int,\n    key: ByteArray,\n    iv: ByteArray,\n    cipherAlgorithm: String = AES_CFB_NOPADDING,\n    sourceFilePath: String,\n    destFilePath: String\n): File? {\n    val sourceFile = File(sourceFilePath)\n    val destFile = File(destFilePath)\n\n    if (sourceFile.exists() && sourceFile.isFile) {\n        if (!destFile.parentFile!!.exists()) destFile.parentFile!!.mkdirs()\n        destFile.createNewFile()\n\n        val inputStream = FileInputStream(sourceFile)\n        val outputStream = FileOutputStream(destFile)\n        val cipher = initCipher(mode, key, iv, cipherAlgorithm)\n        val cin = CipherInputStream(inputStream, cipher)\n\n        val b = ByteArray(1024)\n        var read: Int\n        do {\n            read = cin.read(b)\n            if (read > 0)\n                outputStream.write(b, 0, read)\n        } while (read > 0)\n\n        outputStream.flush()\n        cin.close()\n        inputStream.close()\n        outputStream.close()\n\n        return destFile\n    }\n    return null\n}"
  },
  {
    "path": "yenaly_libs/src/main/java/com/yenaly/yenaly_libs/utils/_GsonUtil.kt",
    "content": "package com.yenaly.yenaly_libs.utils\n\nimport com.google.gson.GsonBuilder\nimport kotlin.reflect.javaType\nimport kotlin.reflect.typeOf\n\nval gson by unsafeLazy {\n    GsonBuilder().create()!!\n}\n\n@OptIn(ExperimentalStdlibApi::class)\ninline fun <reified T> String.fromJson(): T {\n    return gson.fromJson(this, typeOf<T>().javaType)\n}\n\nfun Any?.toJson(): String {\n    return gson.toJson(this)\n}"
  },
  {
    "path": "yenaly_libs/src/main/java/com/yenaly/yenaly_libs/utils/_IntentUtil.kt",
    "content": "@file:Suppress(\"unused\")\n\npackage com.yenaly.yenaly_libs.utils\n\nimport android.app.Activity\nimport android.app.Service\nimport android.content.ActivityNotFoundException\nimport android.content.Context\nimport android.content.Intent\nimport android.net.Uri\nimport android.os.Bundle\nimport android.provider.Settings\nimport androidx.core.os.bundleOf\nimport androidx.fragment.app.Fragment\n\n/**\n *  快捷启动Activity\n *\n *  @param Ava    泛型，继承于Activity\n *  @param values 需要传过去的值\n *  @param flag   flag (optional)\n *  @param extra  附带的bundle (optional)\n */\ninline fun <reified Ava : Activity> Activity.startActivity(\n    vararg values: Pair<String, Any?>,\n    flag: Int? = null,\n    extra: Bundle? = null,\n) = startActivity(getIntent<Ava>(flag, extra, *values))\n\n/**\n *  快捷启动Activity\n *\n *  @param Ava    泛型，继承于Activity\n *  @param flag   flag (optional)\n */\ninline fun <reified Ava : Activity> Activity.startActivity(\n    flag: Int? = null,\n    extra: Bundle? = null,\n) = Intent(this, Ava::class.java).apply {\n    flag?.let { flags = it }\n    extra?.let { putExtras(it) }\n    startActivity(this)\n}\n\n/**\n *  快捷启动Service\n *\n *  @param S      泛型，继承于Service\n *  @param flag   flag (optional)\n *  @param extra  附带的bundle (optional)\n *  @param values 需要传过去的值\n */\ninline fun <reified S : Service> Activity.startService(\n    vararg values: Pair<String, Any?>,\n    flag: Int? = null,\n    extra: Bundle? = null,\n) = startService(getIntent<S>(flag, extra, *values))\n\n/**\n *  快捷启动Service\n *\n *  @param S      泛型，继承于Service\n *  @param flag   flag (optional)\n */\ninline fun <reified S : Service> Activity.startService(\n    flag: Int? = null,\n    extra: Bundle? = null,\n) = Intent(this, S::class.java).apply {\n    flag?.let { flags = it }\n    extra?.let { putExtras(it) }\n    startService(this)\n}\n\n/**\n *  快捷启动Activity\n *\n *  @param Bella  泛型，继承于Activity\n *  @param flag   flag (optional)\n *  @param extra  附带的bundle (optional)\n *  @param values 需要传过去的值\n */\ninline fun <reified Bella : Activity> Fragment.startActivity(\n    vararg values: Pair<String, Any?>,\n    flag: Int? = null,\n    extra: Bundle? = null,\n) = activity?.let {\n    startActivity(it.getIntent<Bella>(flag, extra, *values))\n}\n\n/**\n *  快捷启动Activity\n *\n *  @param Bella  泛型，继承于Activity\n *  @param flag   flag (optional)\n */\ninline fun <reified Bella : Activity> Fragment.startActivity(\n    flag: Int? = null,\n    extra: Bundle? = null,\n) = activity?.let { activity ->\n    Intent(activity, Bella::class.java).apply {\n        flag?.let { flags = it }\n        extra?.let { putExtras(it) }\n        startActivity(this)\n    }\n}\n\n/**\n *  快捷启动Service\n *\n *  @param S      泛型，继承于Service\n *  @param flag   flag (optional)\n *  @param extra  附带的bundle (optional)\n *  @param values 需要传过去的值\n */\ninline fun <reified S : Service> Fragment.startService(\n    vararg values: Pair<String, Any?>,\n    flag: Int? = null,\n    extra: Bundle? = null,\n) = activity?.let {\n    it.startService(it.getIntent<S>(flag, extra, *values))\n}\n\n/**\n *  快捷启动Service\n *\n *  @param S      泛型，继承于Service\n *  @param flag   flag (optional)\n */\ninline fun <reified S : Service> Fragment.startService(\n    flag: Int? = null,\n    extra: Bundle? = null,\n) = activity?.let { activity ->\n    Intent(activity, S::class.java).apply {\n        flag?.let { flags = it }\n        extra?.let { putExtras(it) }\n        activity.startService(this)\n    }\n}\n\n/**\n *  快捷启动Activity\n *\n *  @param Carol  泛型，继承于Activity\n *  @param flag   flag (optional)\n *  @param extra  附带的bundle (optional)\n *  @param values 需要传过去的值\n */\ninline fun <reified Carol : Activity> Context.startActivity(\n    flag: Int? = null,\n    extra: Bundle? = null,\n    vararg values: Pair<String, Any?>,\n) = startActivity(getIntent<Carol>(flag, extra, *values))\n\n/**\n *  快捷启动Activity\n *\n *  @param Carol  泛型，继承于Activity\n *  @param flag   flag (optional)\n *  @param extra  附带的bundle (optional)\n */\ninline fun <reified Carol : Activity> Context.startActivity(\n    flag: Int? = null,\n    extra: Bundle? = null,\n) = Intent(this, Carol::class.java).apply {\n    flag?.let { flags = it }\n    extra?.let { putExtras(it) }\n    startActivity(this)\n}\n\n/**\n *  快捷启动Service\n *\n *  @param S      泛型，继承于Service\n *  @param flag   flag (optional)\n *  @param extra  附带的bundle (optional)\n *  @param values 需要传过去的值\n */\ninline fun <reified S : Service> Context.startService(\n    flag: Int? = null,\n    extra: Bundle? = null,\n    vararg values: Pair<String, Any?>,\n) = startService(getIntent<S>(flag, extra, *values))\n\n/**\n *  快捷启动Service\n *\n *  @param S      泛型，继承于Service\n *  @param flag   flag (optional)\n *  @param extra  附带的bundle (optional)\n */\ninline fun <reified S : Service> Context.startService(\n    flag: Int? = null,\n    extra: Bundle? = null,\n) = Intent(this, S::class.java).apply {\n    flag?.let { flags = it }\n    extra?.let { putExtras(it) }\n    startService(this)\n}\n\n/**\n * 快捷获取一个携带各种参数的intent\n *\n * @param Diana  泛型，继承于Activity\n * @param flag   flag (optional)\n * @param extra  附带的bundle (optional)\n * @param pairs  需要传过去的值\n */\ninline fun <reified Diana : Context> Context.getIntent(\n    flag: Int? = null,\n    extra: Bundle? = null,\n    vararg pairs: Pair<String, Any?>,\n): Intent = Intent(this, Diana::class.java).apply {\n    flag?.let { flags = it }\n    extra?.let { putExtras(it) }\n    if (pairs.isNotEmpty()) {\n        putExtras(bundleOf(*pairs))\n    }\n}\n\n/**\n * 用委托方式获取activity传来的extra，\n * 基本类型涉及装箱拆箱\n *\n * @param Eileen 泛型\n * @param name 传值的名字\n */\n@Suppress(\"UNCHECKED_CAST\", \"DEPRECATION\")\nfun <Eileen> Activity.intentExtra(name: String) = lazy(LazyThreadSafetyMode.NONE) {\n    intent.extras?.get(name) as? Eileen\n}\n\n/**\n * 用委托方式获取activity传来的extra\n *\n * @param Eileen 泛型\n * @param name 传值的名字\n * @param default 缺省值\n */\n@Suppress(\"UNCHECKED_CAST\", \"DEPRECATION\")\nfun <Eileen> Activity.intentExtra(name: String, default: Eileen) = lazy(LazyThreadSafetyMode.NONE) {\n    intent.extras?.get(name) as? Eileen ?: default\n}\n\n/**\n * 用委托方式获取activity传来的extra，\n * 若空则直接报错\n *\n * @param Eileen 泛型\n * @param name 传值的名字\n */\n@Suppress(\"UNCHECKED_CAST\", \"DEPRECATION\")\nfun <Eileen> Activity.safeIntentExtra(name: String) = lazy(LazyThreadSafetyMode.NONE) {\n    val extra = intent.extras?.get(name) as? Eileen\n    checkNotNull(extra) { \"No intent value for key \\\"$name\\\"\" }\n}\n\n/**\n * 用委托方式获取fragment所属activity传来的extra，\n * 基本类型涉及装箱拆箱\n *\n * @param Yoyi 泛型\n * @param name 传值的名字\n */\n@Suppress(\"UNCHECKED_CAST\", \"DEPRECATION\")\nfun <Yoyi> Fragment.activityIntentExtra(name: String) = lazy(LazyThreadSafetyMode.NONE) {\n    activity?.intent?.extras?.get(name) as? Yoyi\n}\n\n/**\n * 用委托方式获取fragment所属activity传来的extra\n *\n * @param Yoyi 泛型\n * @param name 传值的名字\n * @param default 缺省值\n */\n@Suppress(\"UNCHECKED_CAST\", \"DEPRECATION\")\nfun <Yoyi> Fragment.activityIntentExtra(name: String, default: Yoyi) =\n    lazy(LazyThreadSafetyMode.NONE) {\n        activity?.intent?.extras?.get(name) as? Yoyi ?: default\n    }\n\n/**\n * 用委托方式获取fragment所属activity传来的extra，\n * 若空则直接报错\n *\n * @param Yoyi 泛型\n * @param name 传值的名字\n */\n@Suppress(\"UNCHECKED_CAST\", \"DEPRECATION\")\nfun <Yoyi> Fragment.safeActivityIntentExtra(name: String) = lazy(LazyThreadSafetyMode.NONE) {\n    val extra = activity?.intent?.extras?.get(name) as? Yoyi\n    checkNotNull(extra) { \"No intent value for key \\\"$name\\\"\" }\n}\n\n/**\n * 用委托方式接收arguments，\n * 基本类型涉及装箱拆箱\n *\n * @param Bekki 泛型\n * @param name  传值的名字\n */\n@Suppress(\"UNCHECKED_CAST\", \"DEPRECATION\")\nfun <Bekki> Fragment.arguments(name: String) = lazy(LazyThreadSafetyMode.NONE) {\n    arguments?.get(name) as? Bekki\n}\n\n/**\n * 用委托方式接收arguments\n *\n * @param Bekki   泛型\n * @param name    传值的名字\n * @param default 缺省值\n */\n@Suppress(\"UNCHECKED_CAST\", \"DEPRECATION\")\nfun <Bekki> Fragment.arguments(name: String, default: Bekki) = lazy(LazyThreadSafetyMode.NONE) {\n    arguments?.get(name) as? Bekki ?: default\n}\n\n/**\n * 用委托方式接收arguments，\n * 若空则直接报错\n *\n * @param Bekki   泛型\n * @param name    传值的名字\n */\n@Suppress(\"UNCHECKED_CAST\", \"DEPRECATION\")\nfun <Bekki> Fragment.safeArguments(name: String) = lazy(LazyThreadSafetyMode.NONE) {\n    val argument = arguments?.get(name) as? Bekki\n    checkNotNull(argument) { \"No argument value for key \\\"$name\\\"\" }\n}\n\n/**\n * 通过uri浏览\n *\n * @param uri uri地址\n */\ninfix fun Activity.browse(uri: String) {\n    val mUri = Uri.parse(uri)\n    val intent = Intent(Intent.ACTION_VIEW, mUri)\n    startActivity(intent)\n}\n\n/**\n * 通过uri浏览\n *\n * @param uri uri地址\n */\ninfix fun Fragment.browse(uri: String) {\n    val mUri = Uri.parse(uri)\n    val intent = Intent(Intent.ACTION_VIEW, mUri)\n    startActivity(intent)\n}\n\ninfix fun Context.browse(uri: String) {\n    val mUri = Uri.parse(uri)\n    val intent = Intent(Intent.ACTION_VIEW, mUri)\n    intent.flags = Intent.FLAG_ACTIVITY_NEW_TASK\n    startActivity(intent)\n}\n\n/**\n * 快捷给Fragment传值\n *\n * @param params 需要传过去的值\n *\n * @return 带值的本身\n */\nfun <F : Fragment> F.makeBundle(vararg params: Pair<String, Any?>): F {\n    return this.apply {\n        arguments = bundleOf(*params)\n    }\n}\n\n/**\n * Visit app in app store\n *\n * @param packageName default value is current app\n */\nfun Context.openInAppStore(packageName: String = this.packageName) {\n    val intent = Intent(Intent.ACTION_VIEW)\n    try {\n        intent.data = Uri.parse(\"market://details?id=$packageName\")\n        startActivity(intent)\n    } catch (ifPlayStoreNotInstalled: ActivityNotFoundException) {\n        intent.data =\n            Uri.parse(\"https://play.google.com/store/apps/details?id=$packageName\")\n        startActivity(intent)\n    }\n}\n\n/**\n * Open app by [packageName]\n */\nfun Context.openApp(packageName: String) =\n    packageManager.getLaunchIntentForPackage(packageName)?.run { startActivity(this) }\n\n/**\n * Send email\n *\n * @param email the email address be sent to\n * @param subject a constant string holding the desired subject line of a message, @see [Intent.EXTRA_SUBJECT]\n * @param text a constant CharSequence that is associated with the Intent, @see [Intent.EXTRA_TEXT]\n */\nfun Context.sendEmail(email: String, subject: String?, text: String?) {\n    Intent(Intent.ACTION_SENDTO, Uri.parse(\"mailto:$email\")).run {\n        subject?.let { putExtra(Intent.EXTRA_SUBJECT, subject) }\n        text?.let { putExtra(Intent.EXTRA_TEXT, text) }\n        startActivity(this)\n    }\n}\n\n/**\n * Return the Intent with [Settings.ACTION_APPLICATION_DETAILS_SETTINGS]\n */\nfun Context.getAppInfoIntent(packageName: String = this.packageName): Intent =\n    Intent(\n        Settings.ACTION_APPLICATION_DETAILS_SETTINGS,\n        Uri.fromParts(\"package\", packageName, null)\n    ).apply {\n        addFlags(Intent.FLAG_ACTIVITY_NEW_TASK or Intent.FLAG_ACTIVITY_CLEAR_TASK)\n    }\n\n/**\n * Jump to the app info page\n */\nfun Context.goToAppInfoPage(packageName: String = this.packageName) {\n    startActivity(getAppInfoIntent(packageName))\n}"
  },
  {
    "path": "yenaly_libs/src/main/java/com/yenaly/yenaly_libs/utils/_LazyUtil.kt",
    "content": "package com.yenaly.yenaly_libs.utils\n\n/**\n * Android一般用不到[LazyThreadSafetyMode.SYNCHRONIZED]的lazy，\n * 使用[LazyThreadSafetyMode.NONE]更合适。\n */\nfun <T> unsafeLazy(initializer: () -> T) = lazy(LazyThreadSafetyMode.NONE, initializer)"
  },
  {
    "path": "yenaly_libs/src/main/java/com/yenaly/yenaly_libs/utils/span/SpannedTextGenerator.kt",
    "content": "@file:Suppress(\"unused\")\n\npackage com.yenaly.yenaly_libs.utils.span\n\nimport android.graphics.BlurMaskFilter\nimport android.graphics.Typeface\nimport android.graphics.drawable.Drawable\nimport android.text.SpannableStringBuilder\nimport android.text.Spanned\nimport android.text.method.LinkMovementMethod\nimport android.text.style.*\nimport android.view.View\nimport android.widget.TextView\nimport androidx.annotation.ColorInt\nimport androidx.annotation.DrawableRes\nimport androidx.annotation.Px\nimport androidx.core.content.ContextCompat\nimport com.yenaly.yenaly_libs.utils.applicationContext\n\n/**\n * @ProjectName : YenalyModule\n * @Author : Yenaly Liew\n * @Time : 2022/04/27 027 16:28\n * @Description : Description...\n */\nclass SpannedTextGenerator private constructor() {\n\n    class KotlinBuilder(private val flag: Int = Spanned.SPAN_EXCLUSIVE_EXCLUSIVE) {\n\n        private val lineSeparator = System.getProperty(\"line.separator\")\n\n        private val ssb = SpannableStringBuilder()\n\n        private var start = 0\n        private var end = 0\n\n        fun addText(\n            text: CharSequence,\n            url: String? = null,\n            isNewLine: Boolean = true,\n            @Px textSize: Int = -1,\n            relativeSize: Float = -1F,\n            @Px firstLineMarginStart: Int = 0,\n            @Px restLineMarginStart: Int = 0,\n            scaleX: Float = -1F,\n            @ColorInt backgroundColor: Int? = null,\n            @ColorInt foregroundColor: Int? = null,\n            @ColorInt quoteColor: Int = YenalyQuoteSpan.STANDARD_COLOR,\n            @Px quoteStripeWidth: Int = YenalyQuoteSpan.STANDARD_STRIPE_WIDTH_PX,\n            @Px quoteGapWidth: Int = YenalyQuoteSpan.STANDARD_GAP_WIDTH_PX,\n            blurRadius: Float = -1F,\n            blurStyle: BlurMaskFilter.Blur = BlurMaskFilter.Blur.NORMAL,\n            isBold: Boolean = false,\n            isItalic: Boolean = false,\n            isUnderLine: Boolean = false,\n            isStrikeThrough: Boolean = false,\n            isSuperscript: Boolean = false,\n            isSubscript: Boolean = false,\n            isQuote: Boolean = false,\n            onClick: OnClickListener? = null\n        ): KotlinBuilder {\n\n            start = ssb.length\n            ssb.append(text)\n            end = ssb.length\n\n            if (textSize >= 0) {\n                ssb.setSpan(AbsoluteSizeSpan(textSize), start, end, flag)\n            }\n            if (relativeSize >= 0F) {\n                ssb.setSpan(RelativeSizeSpan(relativeSize), start, end, flag)\n            }\n            ssb.setSpan(\n                LeadingMarginSpan.Standard(firstLineMarginStart, restLineMarginStart),\n                start,\n                end,\n                flag\n            )\n            if (scaleX >= 0F) {\n                ssb.setSpan(ScaleXSpan(scaleX), start, end, flag)\n            }\n            if (backgroundColor != null) {\n                ssb.setSpan(BackgroundColorSpan(backgroundColor), start, end, flag)\n            }\n            if (foregroundColor != null) {\n                ssb.setSpan(ForegroundColorSpan(foregroundColor), start, end, flag)\n            }\n            if (blurRadius >= 0F) {\n                ssb.setSpan(MaskFilterSpan(BlurMaskFilter(blurRadius, blurStyle)), start, end, flag)\n            }\n\n            if (isBold) {\n                ssb.setSpan(StyleSpan(Typeface.BOLD), start, end, flag)\n            }\n            if (isItalic) {\n                ssb.setSpan(StyleSpan(Typeface.ITALIC), start, end, flag)\n            }\n            if (isUnderLine) {\n                ssb.setSpan(UnderlineSpan(), start, end, flag)\n            }\n            if (isStrikeThrough) {\n                ssb.setSpan(StrikethroughSpan(), start, end, flag)\n            }\n            if (isSuperscript) {\n                ssb.setSpan(SuperscriptSpan(), start, end, flag)\n            }\n            if (isSubscript) {\n                ssb.setSpan(SubscriptSpan(), start, end, flag)\n            }\n            if (isQuote) {\n                ssb.setSpan(\n                    YenalyQuoteSpan(quoteColor, quoteStripeWidth, quoteGapWidth),\n                    start,\n                    end,\n                    flag\n                )\n            }\n            if (isNewLine) {\n                ssb.append(lineSeparator)\n            }\n            url?.let {\n                ssb.setSpan(URLSpan(it), start, end, flag)\n            }\n            onClick?.let {\n                val clickableSpan = object : ClickableSpan() {\n                    override fun onClick(widget: View) {\n                        it.onClick(widget, url, text)\n                    }\n                }\n                ssb.setSpan(clickableSpan, start, end, flag)\n            }\n\n            return this\n        }\n\n        fun addNewLine(@Px textSize: Int = -1): KotlinBuilder {\n            addText(lineSeparator as CharSequence, textSize = textSize, isNewLine = false)\n            return this\n        }\n\n        fun addImage(\n            @DrawableRes resId: Int,\n            verticalAlignment: Int = YenalyImageSpan.ALIGN_MIDDLE,\n            @Px width: Int = -1,\n            @Px height: Int = -1,\n            scaleX: Float = 1F,\n            scaleY: Float = 1F,\n            marginLeft: Int = 0,\n            marginRight: Int = 0,\n            fontWidthMultiple: Float = -1F,\n            isNewLine: Boolean = false\n        ): KotlinBuilder {\n            val drawable = ContextCompat.getDrawable(applicationContext, resId)\n            addImage(\n                drawable,\n                verticalAlignment,\n                width,\n                height,\n                scaleX,\n                scaleY,\n                marginLeft,\n                marginRight,\n                fontWidthMultiple,\n                isNewLine\n            )\n            return this\n        }\n\n        fun addImage(\n            drawable: Drawable?,\n            verticalAlignment: Int = YenalyImageSpan.ALIGN_MIDDLE,\n            @Px width: Int = -1,\n            @Px height: Int = -1,\n            scaleX: Float = 1F,\n            scaleY: Float = 1F,\n            marginLeft: Int = 0,\n            marginRight: Int = 0,\n            fontWidthMultiple: Float = -1F,\n            isNewLine: Boolean = false\n        ): KotlinBuilder {\n            val start = ssb.length\n            ssb.append(\"[image]\")\n            val end = ssb.length\n\n            drawable?.let {\n                val boundWidth = if (width >= 0) width else it.intrinsicWidth\n                val boundHeight = if (height >= 0) height else it.intrinsicHeight\n                val scaleWidth = (boundWidth * scaleX).toInt()\n                val scaleHeight = (boundHeight * scaleY).toInt()\n                it.setBounds(0, 0, scaleWidth, scaleHeight)\n                ssb.setSpan(\n                    YenalyImageSpan(\n                        it,\n                        verticalAlignment,\n                        fontWidthMultiple,\n                        marginLeft,\n                        marginRight\n                    ), start, end, flag\n                )\n            }\n\n            if (isNewLine) {\n                ssb.append(lineSeparator)\n            }\n            return this\n        }\n\n        fun getText() = ssb\n\n        fun showIn(textView: TextView): KotlinBuilder {\n\n            if (textView.movementMethod == null) {\n                textView.movementMethod = LinkMovementMethod.getInstance()\n            }\n\n            textView.text = ssb\n\n            return this\n        }\n    }\n\n    class JavaBuilder(private val flag: Int = Spanned.SPAN_EXCLUSIVE_EXCLUSIVE) {\n\n        private val lineSeparator = System.getProperty(\"line.separator\")\n\n        private val ssb = SpannableStringBuilder()\n\n        private var text: CharSequence = \"\"\n\n        private var url: String? = null\n\n        @Px\n        private var textSize: Int = -1\n\n        private var relativeSize: Float = -1F\n\n        @Px\n        private var firstLineMarginStart: Int = 0\n\n        @Px\n        private var restLineMarginStart: Int = 0\n        private var scaleX: Float = -1F\n\n        @ColorInt\n        private var backgroundColor: Int? = null\n\n        @ColorInt\n        private var foregroundColor: Int? = null\n\n        @ColorInt\n        private var quoteColor: Int = YenalyQuoteSpan.STANDARD_COLOR\n\n        @Px\n        private var quoteStripeWidth: Int = YenalyQuoteSpan.STANDARD_STRIPE_WIDTH_PX\n\n        @Px\n        private var quoteGapWidth: Int = YenalyQuoteSpan.STANDARD_GAP_WIDTH_PX\n        private var blurRadius: Float = -1F\n        private var blurStyle: BlurMaskFilter.Blur = BlurMaskFilter.Blur.NORMAL\n\n        private var isBold: Boolean = false\n        private var isItalic: Boolean = false\n        private var isUnderLine: Boolean = false\n        private var isStrikeThrough: Boolean = false\n        private var isSuperscript: Boolean = false\n        private var isSubscript: Boolean = false\n        private var isQuote: Boolean = false\n\n        private var onClick: OnClickListener? = null\n\n        fun addText(text: CharSequence): JavaBuilder {\n            if (this.text.isNotEmpty()) setSpan()\n            this.text = text\n            return this\n        }\n\n        fun addTextLine(text: CharSequence): JavaBuilder {\n            if (this.text.isNotEmpty()) setSpan()\n            val sb = StringBuilder(text)\n            this.text = sb.append(lineSeparator)\n            return this\n        }\n\n        fun bold(): JavaBuilder {\n            isBold = true\n            return this\n        }\n\n        fun italic(): JavaBuilder {\n            isItalic = true\n            return this\n        }\n\n        fun underline(): JavaBuilder {\n            isUnderLine = true\n            return this\n        }\n\n        fun strikeThrough(): JavaBuilder {\n            isStrikeThrough = true\n            return this\n        }\n\n        fun superscript(): JavaBuilder {\n            isSuperscript = true\n            return this\n        }\n\n        fun subscript(): JavaBuilder {\n            isSubscript = true\n            return this\n        }\n\n        fun quote(): JavaBuilder {\n            isQuote = true\n            return this\n        }\n\n        @JvmOverloads\n        fun blur(blurRadius: Float, blurStyle: BlurMaskFilter.Blur = this.blurStyle): JavaBuilder {\n            this.blurRadius = blurRadius\n            this.blurStyle = blurStyle\n            return this\n        }\n\n        fun textSize(@Px size: Int): JavaBuilder {\n            this.textSize = size\n            return this\n        }\n\n        fun relativeSize(proportion: Float): JavaBuilder {\n            this.relativeSize = proportion\n            return this\n        }\n\n        fun scaleX(proportion: Float): JavaBuilder {\n            this.scaleX = proportion\n            return this\n        }\n\n        fun firstLineMarginStart(margin: Int): JavaBuilder {\n            this.firstLineMarginStart = margin\n            return this\n        }\n\n        fun restLineMarginStart(margin: Int): JavaBuilder {\n            this.restLineMarginStart = margin\n            return this\n        }\n\n        fun backgroundColor(@ColorInt color: Int): JavaBuilder {\n            this.backgroundColor = color\n            return this\n        }\n\n        fun foregroundColor(@ColorInt color: Int): JavaBuilder {\n            this.foregroundColor = color\n            return this\n        }\n\n        fun quoteStripeWidth(width: Int): JavaBuilder {\n            this.quoteStripeWidth = width\n            return this\n        }\n\n        fun quoteGapWidth(width: Int): JavaBuilder {\n            this.quoteGapWidth = width\n            return this\n        }\n\n        fun url(url: String): JavaBuilder {\n            this.url = url\n            return this\n        }\n\n        fun onClick(onClick: OnClickListener): JavaBuilder {\n            this.onClick = onClick\n            return this\n        }\n\n        private fun setSpan() {\n            val start = ssb.length\n            ssb.append(text)\n            val end = ssb.length\n\n            if (textSize >= 0) {\n                ssb.setSpan(AbsoluteSizeSpan(textSize), start, end, flag)\n                textSize = -1\n            }\n            if (relativeSize >= 0F) {\n                ssb.setSpan(RelativeSizeSpan(relativeSize), start, end, flag)\n                relativeSize = -1F\n            }\n            ssb.setSpan(\n                LeadingMarginSpan.Standard(firstLineMarginStart, restLineMarginStart),\n                start,\n                end,\n                flag\n            )\n            if (scaleX >= 0F) {\n                ssb.setSpan(ScaleXSpan(scaleX), start, end, flag)\n                scaleX = -1F\n            }\n            if (backgroundColor != null) {\n                ssb.setSpan(BackgroundColorSpan(backgroundColor!!), start, end, flag)\n                backgroundColor = null\n            }\n            if (foregroundColor != null) {\n                ssb.setSpan(ForegroundColorSpan(foregroundColor!!), start, end, flag)\n                foregroundColor = null\n            }\n            if (blurRadius >= 0F) {\n                ssb.setSpan(MaskFilterSpan(BlurMaskFilter(blurRadius, blurStyle)), start, end, flag)\n                blurRadius = -1F\n            }\n            if (isBold) {\n                ssb.setSpan(StyleSpan(Typeface.BOLD), start, end, flag)\n                isBold = false\n            }\n            if (isItalic) {\n                ssb.setSpan(StyleSpan(Typeface.ITALIC), start, end, flag)\n                isItalic = false\n            }\n            if (isUnderLine) {\n                ssb.setSpan(UnderlineSpan(), start, end, flag)\n                isUnderLine = false\n            }\n            if (isStrikeThrough) {\n                ssb.setSpan(StrikethroughSpan(), start, end, flag)\n                isStrikeThrough = false\n            }\n            if (isSuperscript) {\n                ssb.setSpan(SuperscriptSpan(), start, end, flag)\n                isSuperscript = false\n            }\n            if (isSubscript) {\n                ssb.setSpan(SubscriptSpan(), start, end, flag)\n                isSubscript = false\n            }\n            if (isQuote) {\n                ssb.setSpan(\n                    YenalyQuoteSpan(quoteColor, quoteStripeWidth, quoteGapWidth),\n                    start,\n                    end,\n                    flag\n                )\n                isQuote = false\n            }\n            url?.let {\n                ssb.setSpan(URLSpan(it), start, end, flag)\n                url = null\n            }\n            onClick?.let {\n                val clickableSpan = object : ClickableSpan() {\n                    override fun onClick(widget: View) {\n                        it.onClick(widget, url, text)\n                    }\n                }\n                ssb.setSpan(clickableSpan, start, end, flag)\n            }\n        }\n\n        fun getText(): SpannableStringBuilder {\n            setSpan()\n            return ssb\n        }\n\n        fun showIn(textView: TextView): JavaBuilder {\n            setSpan()\n\n            if (textView.movementMethod == null) {\n                textView.movementMethod = LinkMovementMethod.getInstance()\n            }\n\n            textView.text = ssb\n\n            return this\n        }\n    }\n\n    interface OnClickListener {\n        fun onClick(widget: View, url: String?, text: CharSequence)\n    }\n}"
  },
  {
    "path": "yenaly_libs/src/main/java/com/yenaly/yenaly_libs/utils/span/YenalyImageSpan.kt",
    "content": "@file:Suppress(\"unused\")\n\npackage com.yenaly.yenaly_libs.utils.span\n\nimport android.graphics.Canvas\nimport android.graphics.Paint\nimport android.graphics.drawable.Drawable\nimport android.text.style.ImageSpan\n\n/**\n * Created by luyao\n * on 2019/8/14 9:17\n */\nclass YenalyImageSpan(\n    drawable: Drawable,\n    verticalAlignment: Int = ALIGN_MIDDLE,\n    fontWidthMultiple: Float = -1f,\n    marginLeft: Int = 0,\n    marginRight: Int = 0\n) : ImageSpan(drawable, verticalAlignment) {\n\n\n    companion object {\n        const val ALIGN_MIDDLE = -100 // 不要和父类重复\n\n        /**\n         * A constant indicating that the bottom of this span should be aligned\n         * with the bottom of the surrounding text, i.e., at the same level as the\n         * lowest descender in the text.\n         */\n        const val ALIGN_BOTTOM = 0\n\n        /**\n         * A constant indicating that the bottom of this span should be aligned\n         * with the baseline of the surrounding text.\n         */\n        const val ALIGN_BASELINE = 1\n    }\n\n    private val sVerticalAlignment = verticalAlignment\n    private val mFontWidthMultiple = fontWidthMultiple\n    private val mMarginLeft = marginLeft\n    private val mMarginRight = marginRight\n    private var mAvoidSuperChangeFontMetrics = false\n    private var mWidth: Int = 0\n\n    override fun getSize(\n        paint: Paint,\n        text: CharSequence?,\n        start: Int,\n        end: Int,\n        fm: Paint.FontMetricsInt?\n    ): Int {\n        mWidth = if (mAvoidSuperChangeFontMetrics) drawable.bounds.right\n        else super.getSize(paint, text, start, end, fm)\n\n        if (mFontWidthMultiple > 0) {\n            mWidth = (paint.measureText(\"子\") * mFontWidthMultiple).toInt()\n        }\n\n        if ((mMarginLeft > 0) or (mMarginRight > 0)) {\n            mWidth = drawable.bounds.right + mMarginLeft + mMarginRight\n        }\n\n        return mWidth\n    }\n\n    override fun draw(\n        canvas: Canvas,\n        text: CharSequence?,\n        start: Int,\n        end: Int,\n        x: Float,\n        top: Int,\n        y: Int,\n        bottom: Int,\n        paint: Paint\n    ) {\n        if (sVerticalAlignment == ALIGN_MIDDLE) {\n            canvas.save()\n            val fontMetricsInt = paint.fontMetricsInt\n            val fontTop = y + fontMetricsInt.top\n            val fontMetricsHeight = fontMetricsInt.bottom - fontMetricsInt.top\n            val iconHeight = drawable.bounds.bottom - drawable.bounds.top\n            val iconTop = fontTop + (fontMetricsHeight - iconHeight) / 2\n            canvas.translate(x + mMarginLeft, iconTop.toFloat())\n            drawable.draw(canvas)\n            canvas.restore()\n        } else\n            super.draw(canvas, text, start, end, x + mMarginLeft, top, y, bottom, paint)\n    }\n}"
  },
  {
    "path": "yenaly_libs/src/main/java/com/yenaly/yenaly_libs/utils/span/YenalyQuoteSpan.kt",
    "content": "@file:Suppress(\"unused\")\n\npackage com.yenaly.yenaly_libs.utils.span\n\nimport android.graphics.Canvas\nimport android.graphics.Paint\nimport android.text.Layout\nimport android.text.style.LeadingMarginSpan\nimport androidx.annotation.ColorInt\n\n/**\n * @ProjectName : YenalyModule\n * @Author : Yenaly Liew\n * @Time : 2022/04/28 028 22:37\n * @Description : Description...\n */\nclass YenalyQuoteSpan @JvmOverloads constructor(\n    @ColorInt private val color: Int = STANDARD_COLOR,\n    private val stripeWidth: Int = STANDARD_STRIPE_WIDTH_PX,\n    private val gapWidth: Int = STANDARD_GAP_WIDTH_PX\n) : LeadingMarginSpan {\n\n    /**\n     * Get the color of the quote stripe.\n     *\n     * @return the color of the quote stripe.\n     */\n    @ColorInt\n    fun getColor() = this.color\n\n    /**\n     * Get the width of the quote stripe.\n     *\n     * @return the width of the quote stripe.\n     */\n    fun getStripeWidth() = this.stripeWidth\n\n    /**\n     * Get the width of the gap between the stripe and the text.\n     *\n     * @return the width of the gap between the stripe and the text.\n     */\n    fun getGapWidth() = this.gapWidth\n\n    override fun getLeadingMargin(first: Boolean): Int {\n        return stripeWidth + gapWidth\n    }\n\n    override fun drawLeadingMargin(\n        c: Canvas,\n        p: Paint,\n        x: Int,\n        dir: Int,\n        top: Int,\n        baseline: Int,\n        bottom: Int,\n        text: CharSequence,\n        start: Int,\n        end: Int,\n        first: Boolean,\n        layout: Layout\n    ) {\n        val style = p.style\n        val color = p.color\n\n        p.style = Paint.Style.FILL\n        p.color = this.color\n\n        c.drawRect(\n            x.toFloat(),\n            top.toFloat(),\n            (x + dir * this.stripeWidth).toFloat(),\n            bottom.toFloat(),\n            p\n        )\n\n        p.style = style\n        p.color = color\n    }\n\n    companion object {\n        /**\n         * Default stripe width in pixels.\n         */\n        const val STANDARD_STRIPE_WIDTH_PX = 2\n\n        /**\n         * Default gap width in pixels.\n         */\n        const val STANDARD_GAP_WIDTH_PX = 2\n\n        /**\n         * Default color for the quote stripe.\n         */\n        @ColorInt\n        const val STANDARD_COLOR = -0xffff01\n    }\n}"
  },
  {
    "path": "yenaly_libs/src/main/java/com/yenaly/yenaly_libs/utils/view/AppBarLayoutStateChangeListener.kt",
    "content": "package com.yenaly.yenaly_libs.utils.view\n\nimport com.google.android.material.appbar.AppBarLayout\nimport kotlinx.coroutines.channels.awaitClose\nimport kotlinx.coroutines.flow.Flow\nimport kotlinx.coroutines.flow.callbackFlow\nimport kotlin.math.abs\n\nfun AppBarLayout.offsetChanges(): Flow<Int> {\n    return callbackFlow {\n        val listener = AppBarLayout.OnOffsetChangedListener { _, verticalOffset ->\n            trySend(verticalOffset)\n        }\n        addOnOffsetChangedListener(listener)\n        awaitClose { removeOnOffsetChangedListener(listener) }\n    }\n}\n\n/**\n * 用于监听AppBarLayout是否展开或折叠\n *\n * 从[AppBarLayout.addOnOffsetChangedListener]中调用该类\n *\n * @author Yenaly Liew\n * @time 2022/06/01 001 16:38\n */\nabstract class AppBarLayoutStateChangeListener : AppBarLayout.OnOffsetChangedListener {\n\n    enum class State {\n        EXPANDED, // 展开\n        COLLAPSED, // 折叠\n        INTERMEDIATE; // 中间态\n    }\n\n    private var mCurrentState: State = State.INTERMEDIATE\n\n    override fun onOffsetChanged(appBarLayout: AppBarLayout, verticalOffset: Int) {\n        when {\n            verticalOffset == 0 -> {\n                if (mCurrentState != State.EXPANDED) {\n                    onStateChanged(appBarLayout, State.EXPANDED)\n                }\n                mCurrentState = State.EXPANDED\n            }\n\n            abs(verticalOffset) >= appBarLayout.totalScrollRange -> {\n                if (mCurrentState != State.COLLAPSED) {\n                    onStateChanged(appBarLayout, State.COLLAPSED)\n                }\n                mCurrentState = State.COLLAPSED\n            }\n\n            else -> {\n                if (mCurrentState != State.INTERMEDIATE) {\n                    onStateChanged(appBarLayout, State.INTERMEDIATE)\n                }\n                mCurrentState = State.INTERMEDIATE\n            }\n        }\n    }\n\n    abstract fun onStateChanged(appBarLayout: AppBarLayout, state: State)\n}"
  },
  {
    "path": "yenaly_libs/src/main/java/com/yenaly/yenaly_libs/utils/view/BottomNavViewMediator.kt",
    "content": "package com.yenaly.yenaly_libs.utils.view\n\nimport android.util.SparseIntArray\nimport androidx.annotation.IdRes\nimport androidx.core.util.set\nimport androidx.fragment.app.Fragment\nimport androidx.fragment.app.FragmentActivity\nimport androidx.recyclerview.widget.RecyclerView\nimport androidx.viewpager2.adapter.FragmentStateAdapter\nimport androidx.viewpager2.widget.ViewPager2\nimport com.google.android.material.bottomnavigation.BottomNavigationView\nimport com.google.android.material.navigation.NavigationBarView\nimport com.yenaly.yenaly_libs.utils.activity\n\n/**\n * @author Yenaly Liew\n * @time 2022/08/03 003 21:04\n */\nclass BottomNavViewMediator(\n    private val bottomNavigationView: BottomNavigationView,\n    private val viewPager2: ViewPager2,\n    private val itemIds: IntArray,\n    var smoothScroll: Boolean = true,\n    private val interfaceCombine: ICombineItemIdWithFragment,\n) {\n\n    private val fragmentActivity = bottomNavigationView.context.activity as? FragmentActivity\n        ?: throw IllegalStateException(\"context cannot be cast to FragmentActivity!\")\n\n    private var currentFragment: Fragment? = null\n    private var onFragmentChangedListener: OnFragmentChangedListener? = null\n\n    private val itemIdsSparseArray = SparseIntArray()\n\n    private var attached = false\n\n    private var viewPager2Adapter: RecyclerView.Adapter<*>? = null\n    private var onItemSelectedListener: NavigationBarView.OnItemSelectedListener? = null\n    private var onPageChangeCallback: ViewPager2.OnPageChangeCallback? = null\n\n    fun attach(): BottomNavViewMediator {\n        check(!attached) { \"${javaClass.simpleName} is already attached\" }\n        check(itemIds.isNotEmpty()) { \"fragment list could not be empty!\" }\n\n        itemIds.forEachIndexed { index, itemId -> itemIdsSparseArray[itemId] = index }\n\n        viewPager2Adapter = object : FragmentStateAdapter(fragmentActivity) {\n            override fun getItemCount() = itemIds.size\n            override fun createFragment(position: Int): Fragment {\n                return interfaceCombine.combine(itemIds[position])\n                    ?: throw IllegalStateException(\"Do you actually set the proper fragment?\")\n            }\n        }\n        attached = true\n\n        onItemSelectedListener = NavigationBarView.OnItemSelectedListener { item ->\n            val currentIndex = itemIdsSparseArray[item.itemId]\n            viewPager2.setCurrentItem(currentIndex, smoothScroll)\n            currentFragment = interfaceCombine.combine(item.itemId)\n            onFragmentChangedListener?.onFragmentChanged(currentFragment!!)\n            return@OnItemSelectedListener true\n        }\n        onPageChangeCallback = object : ViewPager2.OnPageChangeCallback() {\n            override fun onPageSelected(position: Int) {\n                bottomNavigationView.selectedItemId = itemIds[position]\n            }\n        }\n\n        viewPager2.adapter = viewPager2Adapter\n        bottomNavigationView.setOnItemSelectedListener(onItemSelectedListener!!)\n        viewPager2.registerOnPageChangeCallback(onPageChangeCallback!!)\n\n        return this\n    }\n\n    fun toFragment(@IdRes fragmentItemId: Int, smoothScroll: Boolean = false) {\n        check(attached) { \"must call attach() first!\" }\n        val fragmentIndex = itemIdsSparseArray[fragmentItemId]\n        viewPager2.setCurrentItem(fragmentIndex, smoothScroll)\n        bottomNavigationView.selectedItemId = fragmentItemId\n    }\n\n    /**\n     * Set on a callback interface that is optionally\n     * implemented to listen the latest selected fragment.\n     */\n    fun setOnFragmentChangedListener(listener: OnFragmentChangedListener) {\n        this.onFragmentChangedListener = listener\n    }\n\n    fun interface ICombineItemIdWithFragment {\n        fun combine(@IdRes itemId: Int): Fragment?\n    }\n\n    fun interface OnFragmentChangedListener {\n        fun onFragmentChanged(currentFragment: Fragment)\n    }\n}"
  },
  {
    "path": "yenaly_libs/src/main/java/com/yenaly/yenaly_libs/utils/view/BottomNavViewUtil.kt",
    "content": "@file:JvmName(\"BottomNavViewUtil\")\n\npackage com.yenaly.yenaly_libs.utils.view\n\nimport android.view.View\nimport android.view.ViewGroup\nimport androidx.coordinatorlayout.widget.CoordinatorLayout\nimport androidx.core.view.updateLayoutParams\nimport androidx.viewpager2.widget.ViewPager2\nimport com.google.android.material.behavior.HideBottomViewOnScrollBehavior\nimport com.google.android.material.bottomnavigation.BottomNavigationView\n\n/**\n * 解决比如ViewPager2的Fragment内滑动无法传递给Activity的[BottomNavigationView]\n * 的[HideBottomViewOnScrollBehavior]问题，\n * 通过这种方法可以使BNV随[view]的滑动而隐藏或显示\n *\n * @param view 比如ViewPager2之类\n * @param scrollToHide 是否选择滑动隐藏\n */\nfun BottomNavigationView.toggleBottomNavBehavior(view: View, scrollToHide: Boolean) {\n    val layoutParams = this.layoutParams as? CoordinatorLayout.LayoutParams\n        ?: throw IllegalStateException(\"parent needs to be coordinator layout!\")\n    val scrollBehavior = YenalyHideBottomViewOnScrollBehavior<BottomNavigationView>()\n    layoutParams.behavior = scrollBehavior\n    val behavior = layoutParams.behavior as YenalyHideBottomViewOnScrollBehavior\n    behavior.slideUp(this)\n    if (scrollToHide) {\n        view.updateLayoutParams<ViewGroup.MarginLayoutParams> {\n            setMargins(leftMargin, topMargin, rightMargin, 0)\n        }\n    } else {\n        layoutParams.behavior = null\n        view.updateLayoutParams<ViewGroup.MarginLayoutParams> {\n            setMargins(\n                leftMargin, topMargin, rightMargin,\n                this@toggleBottomNavBehavior.measuredHeight + layoutParams.bottomMargin\n            )\n        }\n    }\n}\n\nfun BottomNavigationView.attachViewPager2(\n    viewPager2: ViewPager2,\n    smoothScroll: Boolean = true,\n    addAction: SimpleBottomNavViewMediator.() -> Unit\n) = SimpleBottomNavViewMediator(this, viewPager2, smoothScroll).apply(addAction).attach()"
  },
  {
    "path": "yenaly_libs/src/main/java/com/yenaly/yenaly_libs/utils/view/BottomNavigationViewMediator.kt",
    "content": "package com.yenaly.yenaly_libs.utils.view\n\nimport android.util.SparseIntArray\nimport androidx.annotation.IdRes\nimport androidx.core.util.set\nimport androidx.fragment.app.Fragment\nimport androidx.fragment.app.FragmentActivity\nimport androidx.recyclerview.widget.RecyclerView\nimport androidx.viewpager2.adapter.FragmentStateAdapter\nimport androidx.viewpager2.widget.ViewPager2\nimport com.google.android.material.bottomnavigation.BottomNavigationView\nimport com.google.android.material.navigation.NavigationBarView\nimport com.yenaly.yenaly_libs.utils.activity\n\n/**\n * A mediator to link a BottomNavigationView with a ViewPager2. For FragmentActivity only!\n *\n * Instantiating a BottomNavigationViewMediator will only create the mediator object,\n * you must call [attach] on it first to link the BottomNavigationView and the ViewPager2 together.\n *\n * @param fragmentActivity (optional)\n * @param bottomNavigationView\n * @param viewPager2\n * @param itemIdWithFragmentList fragment item id with fragment list\n * @param slide if ViewPager2 needs to slide\n * @param smoothScroll if ViewPager2 scrolls smoothly when BottomNavView is selected\n *\n * @author Yenaly Liew\n * @Time : 2022/06/03 003 11:21\n * @Description : Description...\n */\n@Suppress(\"unused\")\nclass BottomNavigationViewMediator @JvmOverloads constructor(\n    private val fragmentActivity: FragmentActivity,\n    private val bottomNavigationView: BottomNavigationView,\n    private val viewPager2: ViewPager2,\n    private val itemIdWithFragmentList: List<Pair<Int, Fragment>>,\n    var slide: Boolean = true,\n    var smoothScroll: Boolean = true\n) {\n\n    var currentFragment: Fragment? = null\n        private set\n\n    private var listener: OnFragmentChangedListener? = null\n\n    private val fragmentList = itemIdWithFragmentList.map { it.second }\n\n    // 将list存到SparseArray里，方便后续直接通过itemId拿Fragment\n    private val itemIdWithIndexMap = SparseIntArray().also { map ->\n        itemIdWithFragmentList.forEachIndexed { index, pair ->\n            map[pair.first] = index\n        }\n    }\n\n    private var attached = false\n    private var viewPager2Adapter: RecyclerView.Adapter<*>? = null\n\n    private var onItemSelectedListener: NavigationBarView.OnItemSelectedListener? = null\n    private var onPageChangeCallback: ViewPager2.OnPageChangeCallback? = null\n\n    @JvmOverloads\n    constructor(\n        bottomNavigationView: BottomNavigationView,\n        viewPager2: ViewPager2,\n        itemIdWithFragmentList: List<Pair<Int, Fragment>>,\n        slide: Boolean = true,\n        smoothScroll: Boolean = true\n    ) : this(\n        viewPager2.context.activity as? FragmentActivity\n            ?: throw IllegalStateException(\"context cannot be cast to FragmentActivity!\"),\n        bottomNavigationView,\n        viewPager2,\n        itemIdWithFragmentList,\n        slide,\n        smoothScroll\n    )\n\n    fun attach(): BottomNavigationViewMediator {\n        if (attached) {\n            throw IllegalStateException(\"${javaClass.simpleName} is already attached\")\n        }\n        if (itemIdWithFragmentList.isEmpty()) {\n            throw IllegalStateException(\"fragment list could not be empty!\")\n        }\n        viewPager2Adapter = object : FragmentStateAdapter(fragmentActivity) {\n            override fun getItemCount(): Int {\n                return itemIdWithFragmentList.size\n            }\n\n            override fun createFragment(position: Int): Fragment {\n                val softCopyFragmentList: MutableList<Fragment> = ArrayList(fragmentList)\n                return softCopyFragmentList[position]\n            }\n        }\n        attached = true\n\n        onItemSelectedListener = NavigationBarView.OnItemSelectedListener { item ->\n            val currentItem = itemIdWithIndexMap[item.itemId]\n            viewPager2.setCurrentItem(currentItem, smoothScroll)\n            currentFragment = itemIdWithFragmentList[currentItem].second\n            // listener?.onFragmentSelected(itemIdWithFragmentList[currentItem].second)\n            true\n        }\n        onPageChangeCallback = object : ViewPager2.OnPageChangeCallback() {\n            override fun onPageSelected(position: Int) {\n                super.onPageSelected(position)\n                val currentItem = itemIdWithFragmentList[position]\n                bottomNavigationView.selectedItemId = currentItem.first\n                currentFragment = currentItem.second\n                listener?.onFragmentChanged(currentItem.second)\n            }\n        }\n\n        viewPager2.isUserInputEnabled = slide\n        viewPager2.adapter = viewPager2Adapter\n        bottomNavigationView.setOnItemSelectedListener(onItemSelectedListener!!)\n        viewPager2.registerOnPageChangeCallback(onPageChangeCallback!!)\n\n        return this\n    }\n\n    /**\n     * call this to jump to specific fragment\n     *\n     * @return fragment\n     */\n    @JvmOverloads\n    fun jumpToFragment(@IdRes fragmentItemId: Int, smoothScroll: Boolean = false): Fragment {\n        if (onItemSelectedListener == null || onPageChangeCallback == null) {\n            throw IllegalStateException(\"must call attach() first!\")\n        }\n        val fragmentIndex = itemIdWithIndexMap[fragmentItemId]\n        val fragment = itemIdWithFragmentList[fragmentIndex].second\n        viewPager2.setCurrentItem(fragmentIndex, smoothScroll)\n        bottomNavigationView.selectedItemId = fragmentItemId\n        currentFragment = fragment\n        listener?.onFragmentChanged(fragment)\n        return fragment\n    }\n\n    /**\n     * Set on a callback interface that is optionally\n     * implemented to listen the latest selected fragment.\n     */\n    fun setOnFragmentChangedListener(listener: OnFragmentChangedListener) {\n        this.listener = listener\n    }\n\n    /**\n     * A callback interface that is optionally implemented to listen the latest selected fragment.\n     */\n    fun interface OnFragmentChangedListener {\n        fun onFragmentChanged(currentFragment: Fragment)\n    }\n}"
  },
  {
    "path": "yenaly_libs/src/main/java/com/yenaly/yenaly_libs/utils/view/ClickUtil.kt",
    "content": "@file:JvmName(\"ClickUtil\")\n@file:Suppress(\"unused\")\n\npackage com.yenaly.yenaly_libs.utils.view\n\nimport android.view.View\nimport androidx.annotation.IdRes\nimport androidx.lifecycle.Lifecycle\nimport androidx.lifecycle.coroutineScope\nimport kotlinx.coroutines.channels.awaitClose\nimport kotlinx.coroutines.delay\nimport kotlinx.coroutines.flow.Flow\nimport kotlinx.coroutines.flow.callbackFlow\nimport kotlinx.coroutines.flow.launchIn\nimport kotlinx.coroutines.flow.onEach\n\nfun View.clickFlow(): Flow<View> {\n    return callbackFlow {\n        setOnClickListener {\n            trySend(it)\n        }\n        awaitClose { setOnClickListener(null) }\n    }\n}\n\n/**\n * 带生命周期的click\n */\nfun View.click(lifecycle: Lifecycle, onClick: View.OnClickListener) {\n    clickFlow().onEach {\n        onClick.onClick(it)\n    }.launchIn(lifecycle.coroutineScope)\n}\n\n/**\n * 延迟点击\n */\nfun View.clickDelayed(\n    lifecycle: Lifecycle,\n    delayMillis: Long = 500,\n    onClick: View.OnClickListener\n) {\n    clickFlow().onEach {\n        delay(delayMillis)\n        onClick.onClick(it)\n    }.launchIn(lifecycle.coroutineScope)\n}\n\n/**\n * 防止多次点击\n */\nfun View.clickTrigger(\n    lifecycle: Lifecycle,\n    intervalMillis: Long = 500,\n    onClick: View.OnClickListener\n) = ClickTrigger().bind(this, lifecycle, intervalMillis, onClick)\n\n/**\n * 根据条件判断是否可以点击\n *\n * 使用只需要给View设置一个tag，然后在点击时设置tag为true即可\n */\nfun View.clickWithCondition(\n    lifecycle: Lifecycle,\n    @IdRes tag: Int,\n    onClick: View.OnClickListener\n) {\n    // 第一次点击总是成功的\n    setTag(tag, true)\n    clickFlow().onEach {\n        if (it.getTag(tag) == true) {\n            onClick.onClick(it)\n        }\n    }.launchIn(lifecycle.coroutineScope)\n}\n\nprivate class ClickTrigger {\n    private var lastMillis: Long = 0\n\n    fun bind(\n        view: View, lifecycle: Lifecycle,\n        intervalMillis: Long = 500,\n        onClick: View.OnClickListener\n    ) {\n        view.clickFlow().onEach {\n            val currentMillis = System.currentTimeMillis()\n            if (currentMillis - lastMillis < intervalMillis) {\n                return@onEach\n            }\n            lastMillis = currentMillis\n            onClick.onClick(it)\n        }.launchIn(lifecycle.coroutineScope)\n    }\n}"
  },
  {
    "path": "yenaly_libs/src/main/java/com/yenaly/yenaly_libs/utils/view/EditTextUtil.kt",
    "content": "@file:JvmName(\"EditTextUtil\")\n\npackage com.yenaly.yenaly_libs.utils.view\n\nimport android.view.Window\nimport android.widget.EditText\nimport com.yenaly.yenaly_libs.utils.showIme\n\n/**\n * 在EditText上聚焦并显示软键盘\n *\n * @param window window\n */\nfun EditText.showIme(window: Window) = this.let {\n    it.isFocusable = true\n    it.isFocusableInTouchMode = true\n    if (it.hasFocus()) {\n        window.showIme(true)\n    } else {\n        it.requestFocus()\n        repeat(2) {\n            window.showIme(true)\n        }\n    }\n}\n\n/**\n * 在EditText上解除聚焦并隐藏软键盘\n *\n * @param window window\n */\nfun EditText.hideIme(window: Window) = this.let {\n    it.clearFocus()\n    window.showIme(false)\n}"
  },
  {
    "path": "yenaly_libs/src/main/java/com/yenaly/yenaly_libs/utils/view/SimpleBottomNavViewMediator.kt",
    "content": "package com.yenaly.yenaly_libs.utils.view\n\nimport android.util.SparseArray\nimport android.util.SparseIntArray\nimport androidx.annotation.IdRes\nimport androidx.core.util.isNotEmpty\nimport androidx.core.util.set\nimport androidx.fragment.app.Fragment\nimport androidx.fragment.app.FragmentActivity\nimport androidx.recyclerview.widget.RecyclerView\nimport androidx.viewpager2.adapter.FragmentStateAdapter\nimport androidx.viewpager2.widget.ViewPager2\nimport androidx.viewpager2.widget.ViewPager2.OnPageChangeCallback\nimport com.google.android.material.bottomnavigation.BottomNavigationView\nimport com.google.android.material.navigation.NavigationBarView\nimport com.yenaly.yenaly_libs.utils.activity\n\n/**\n * @project Han1meViewer\n * @author Yenaly Liew\n * @time 2022/11/09 009 14:39\n */\nclass SimpleBottomNavViewMediator constructor(\n    private val bottomNavigationView: BottomNavigationView,\n    private val viewPager2: ViewPager2,\n    var smoothScroll: Boolean = true,\n) {\n\n    private val fragmentActivity = bottomNavigationView.context.activity as? FragmentActivity\n        ?: throw IllegalStateException(\"context cannot be cast to FragmentActivity!\")\n\n    private var currentFragment: Fragment? = null\n    private var onFragmentChangedListener: OnFragmentChangedListener? = null\n\n    private var attached = false\n\n    private var index = 0\n    private val newFragmentMap = SparseArray<NewFragment>()\n    private val indexMap = SparseIntArray()\n    private val newFragmentList = mutableListOf<Pair<Int, NewFragment>>()\n\n    private var viewPager2Adapter: RecyclerView.Adapter<*>? = null\n    private var onItemSelectedListener: NavigationBarView.OnItemSelectedListener? = null\n    private var onPageChangeCallback: OnPageChangeCallback? = null\n\n    fun attach() = apply {\n        check(!attached) { \"${javaClass.simpleName} is already attached\" }\n        check(newFragmentMap.isNotEmpty()) { \"fragment list could not be empty!\" }\n\n        viewPager2Adapter = object : FragmentStateAdapter(fragmentActivity) {\n            override fun getItemCount(): Int {\n                return index\n            }\n\n            override fun createFragment(position: Int): Fragment {\n                return newFragmentList[position].second.invoke()\n            }\n        }\n\n        onItemSelectedListener = NavigationBarView.OnItemSelectedListener { item ->\n            val currentIndex = indexMap[item.itemId]\n            viewPager2.setCurrentItem(currentIndex, smoothScroll)\n            return@OnItemSelectedListener true\n        }\n\n        onPageChangeCallback = object : OnPageChangeCallback() {\n            override fun onPageSelected(position: Int) {\n                bottomNavigationView.selectedItemId = newFragmentList[position].first\n                onFragmentChangedListener?.onFragmentChanged(\n                    fragmentActivity.supportFragmentManager.fragments.find { it.isResumed }\n                )\n            }\n        }\n\n        viewPager2.adapter = viewPager2Adapter\n        bottomNavigationView.setOnItemSelectedListener(onItemSelectedListener!!)\n        viewPager2.registerOnPageChangeCallback(onPageChangeCallback!!)\n\n        attached = true\n    }\n\n    fun detach() = apply {\n        TODO(\"I am lazy!\")\n    }\n\n    /**\n     * For example:\n     *\n     * `R.id.xxx with { XXFragment() }`\n     */\n    infix fun @receiver:IdRes Int.with(newFragment: NewFragment) {\n        newFragmentMap[this] = newFragment\n        indexMap[this] = index\n        newFragmentList += this to newFragment\n        index++\n    }\n\n    /**\n     * Set on a callback interface that is optionally\n     * implemented to listen the latest selected fragment.\n     */\n    fun setOnFragmentChangedListener(listener: OnFragmentChangedListener) {\n        this.onFragmentChangedListener = listener\n    }\n\n    fun interface OnFragmentChangedListener {\n        fun onFragmentChanged(currentFragment: Fragment?)\n    }\n}"
  },
  {
    "path": "yenaly_libs/src/main/java/com/yenaly/yenaly_libs/utils/view/SimpleFragmentStateAdapter.kt",
    "content": "package com.yenaly.yenaly_libs.utils.view\n\nimport androidx.fragment.app.Fragment\nimport androidx.fragment.app.FragmentActivity\nimport androidx.fragment.app.FragmentManager\nimport androidx.lifecycle.Lifecycle\nimport androidx.viewpager2.adapter.FragmentStateAdapter\n\n/**\n * @author Yenaly Liew\n * @time 2022/11/09 009 14:05\n */\nclass SimpleFragmentStateAdapter : FragmentStateAdapter {\n\n    private val newFragmentList = mutableListOf<NewFragment>()\n\n    constructor(fragmentActivity: FragmentActivity) :\n            super(fragmentActivity)\n\n    constructor(fragment: Fragment) :\n            super(fragment)\n\n    constructor(fragmentManager: FragmentManager, lifecycle: Lifecycle) :\n            super(fragmentManager, lifecycle)\n\n    override fun getItemCount(): Int {\n        return newFragmentList.size\n    }\n\n    override fun createFragment(position: Int): Fragment {\n        return newFragmentList[position].invoke()\n    }\n\n    fun addFragment(newFragment: NewFragment) {\n        newFragmentList.add(newFragment)\n    }\n}"
  },
  {
    "path": "yenaly_libs/src/main/java/com/yenaly/yenaly_libs/utils/view/TabLayoutUtil.kt",
    "content": "package com.yenaly.yenaly_libs.utils.view\n\nimport androidx.viewpager2.widget.ViewPager2\nimport com.google.android.material.tabs.TabLayout\nimport com.google.android.material.tabs.TabLayoutMediator\n\ninline fun TabLayout.addOnTabSelectedListener(\n    crossinline onTabUnselected: (TabLayout.Tab) -> Unit = {},\n    crossinline onTabReselected: (TabLayout.Tab) -> Unit = {},\n    crossinline onTabSelect: (TabLayout.Tab) -> Unit,\n) {\n    addOnTabSelectedListener(object : TabLayout.OnTabSelectedListener {\n        override fun onTabSelected(tab: TabLayout.Tab) {\n            onTabSelect.invoke(tab)\n        }\n\n        override fun onTabUnselected(tab: TabLayout.Tab) {\n            onTabUnselected.invoke(tab)\n        }\n\n        override fun onTabReselected(tab: TabLayout.Tab) {\n            onTabReselected.invoke(tab)\n        }\n    })\n}\n\n@Suppress(\"NOTHING_TO_INLINE\")\ninline fun TabLayout.attach(\n    viewPager2: ViewPager2,\n    tabConfigurationStrategy: TabLayoutMediator.TabConfigurationStrategy\n): TabLayoutMediator {\n    return TabLayoutMediator(this, viewPager2, tabConfigurationStrategy).also { it.attach() }\n}"
  },
  {
    "path": "yenaly_libs/src/main/java/com/yenaly/yenaly_libs/utils/view/ViewPagerUtil.kt",
    "content": "@file:JvmName(\"ViewPagerUtil\")\n\npackage com.yenaly.yenaly_libs.utils.view\n\nimport androidx.core.view.get\nimport androidx.fragment.app.Fragment\nimport androidx.fragment.app.FragmentActivity\nimport androidx.recyclerview.widget.RecyclerView\nimport androidx.viewpager2.widget.ViewPager2\n\ntypealias NewFragment = () -> Fragment\n\ninline val ViewPager2.innerRecyclerView\n    get() = this[0] as? RecyclerView\n\n/**\n * 如果直接给ViewPager2设置overScrollMode会无效，\n * 这种方式能设置有效的overScrollMode\n */\ninline var ViewPager2.realOverScrollMode: Int\n    get() {\n        return innerRecyclerView?.overScrollMode ?: -1\n    }\n    set(value) {\n        innerRecyclerView?.overScrollMode = value\n    }\n\n/**\n * ViewPager2快速設置FragmentStateAdapter，這裏作用域為FragmentActivity\n */\ninline fun ViewPager2.setUpFragmentStateAdapter(\n    fragmentActivity: FragmentActivity,\n    crossinline addAction: SimpleFragmentStateAdapter.() -> Unit,\n) {\n    adapter = SimpleFragmentStateAdapter(fragmentActivity).apply(addAction)\n}\n\n/**\n * ViewPager2快速設置FragmentStateAdapter，這裏作用域為Fragment\n */\ninline fun ViewPager2.setUpFragmentStateAdapter(\n    fragment: Fragment,\n    crossinline addAction: SimpleFragmentStateAdapter.() -> Unit,\n) {\n    adapter = SimpleFragmentStateAdapter(fragment).apply(addAction)\n}"
  },
  {
    "path": "yenaly_libs/src/main/java/com/yenaly/yenaly_libs/utils/view/ViewUtil.kt",
    "content": "@file:JvmName(\"ViewUtil\")\n@file:Suppress(\"unused\")\n\npackage com.yenaly.yenaly_libs.utils.view\n\nimport android.graphics.Outline\nimport android.graphics.Path\nimport android.graphics.Rect\nimport android.os.Build\nimport android.view.View\nimport android.view.ViewGroup\nimport android.view.ViewGroup.MarginLayoutParams\nimport android.view.ViewOutlineProvider\nimport androidx.core.view.marginBottom\nimport androidx.core.view.marginEnd\nimport androidx.core.view.marginStart\nimport androidx.core.view.marginTop\nimport androidx.core.view.updateLayoutParams\nimport kotlin.math.min\n\n/**\n * 设置view的margins，设置相对位置margins更符合Google标准\n *\n * @param start  view的开始位\n * @param top    view的顶部\n * @param end    view的结束位\n * @param bottom view的底部\n */\nfun View.setMargins(\n    start: Int = marginStart,\n    top: Int = marginTop,\n    end: Int = marginEnd,\n    bottom: Int = marginBottom,\n) = updateLayoutParams<MarginLayoutParams> {\n    setMargins(0, top, 0, bottom)\n    marginStart = start\n    marginEnd = end\n}\n\nfun View.resize(\n    scaleX: Float,\n    scaleY: Float,\n) = updateLayoutParams {\n    width = (width * scaleX).toInt()\n    height = (height * scaleY).toInt()\n}\n\n/**\n * This function is used to remove a view from its parent ViewGroup.\n * It checks if the parent of the view is a ViewGroup and if so, removes the view from it.\n */\nfun View.removeItself() {\n    (parent as? ViewGroup)?.removeView(this)\n}\n\n/**\n * This is a generic function that finds and returns the parent of a view that is of a specific type.\n * It traverses up the view hierarchy until it finds a parent of the specified type.\n * If no parent of the specified type is found, it throws an error.\n *\n * @return The parent of the view that is of the specified type.\n * @throws Error if no parent of the specified type is found.\n */\ninline fun <reified T : View> View.findParent(): T {\n    var parent = parent\n    while (parent != null) {\n        if (parent is T) {\n            return parent\n        }\n        parent = parent.parent\n    }\n    error(\"No parent of type ${T::class.java.simpleName} found\")\n}\n\nfun View.setRoundCorner(radius: Float) {\n    clipToOutline = true\n    outlineProvider = object : ViewOutlineProvider() {\n        override fun getOutline(view: View, outline: Outline) {\n            val min = min(view.width, view.height)\n            val realRadius = if (radius > min / 2) (min / 2).toFloat() else radius\n            outline.setRoundRect(0, 0, view.width, view.height, realRadius)\n        }\n    }\n}\n\nfun View.setRoundCorner(topLeft: Float, topRight: Float, bottomRight: Float, bottomLeft: Float) {\n    clipToOutline = true\n    outlineProvider = object : ViewOutlineProvider() {\n        @Suppress(\"DEPRECATION\")\n        override fun getOutline(view: View, outline: Outline) {\n            val path = Path().apply {\n                addRoundRect(\n                    0f, 0f, view.width.toFloat(), view.height.toFloat(),\n                    floatArrayOf(\n                        topLeft, topLeft,\n                        topRight, topRight,\n                        bottomRight, bottomRight,\n                        bottomLeft, bottomLeft\n                    ),\n                    Path.Direction.CW\n                )\n            }\n            if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.R) {\n                outline.setPath(path)\n            } else {\n                outline.setConvexPath(path)\n            }\n        }\n    }\n}\n\n/**\n * 计算纵向可见百分比\n */\nval View.verticalVisiblePercent: Float\n    get() {\n        val rect = Rect()\n        val visibleHeight = if (getLocalVisibleRect(rect)) rect.height() else 0\n        val fullHeight = with(rect) { getHitRect(rect); this.height() }\n        return if (fullHeight == 0) 0F else visibleHeight.toFloat() / fullHeight\n    }\n\n/**\n * 计算横向可见百分比\n */\nval View.horizontalVisiblePercent: Float\n    get() {\n        val rect = Rect()\n        val visibleWidth = if (getLocalVisibleRect(rect)) rect.width() else 0\n        val fullWidth = with(rect) { getHitRect(rect); this.width() }\n        return if (fullWidth == 0) 0F else visibleWidth.toFloat() / fullWidth\n    }\n\n/**\n * 计算可见百分比\n */\nval View.visiblePercent: Float\n    get() = min(verticalVisiblePercent, horizontalVisiblePercent)"
  },
  {
    "path": "yenaly_libs/src/main/java/com/yenaly/yenaly_libs/utils/view/YenalyHideBottomViewOnScrollBehavior.kt",
    "content": "@file:Suppress(\"unused\", \"MemberVisibilityCanBePrivate\")\n\npackage com.yenaly.yenaly_libs.utils.view\n\nimport android.animation.Animator\nimport android.animation.AnimatorListenerAdapter\nimport android.animation.TimeInterpolator\nimport android.content.Context\nimport android.util.AttributeSet\nimport android.view.View\nimport android.view.ViewGroup.MarginLayoutParams\nimport android.view.ViewPropertyAnimator\nimport androidx.annotation.Dimension\nimport androidx.coordinatorlayout.widget.CoordinatorLayout\nimport androidx.core.view.ViewCompat\nimport androidx.interpolator.view.animation.FastOutLinearInInterpolator\nimport androidx.interpolator.view.animation.LinearOutSlowInInterpolator\n\n/**\n * The [CoordinatorLayout.Behavior] for a View within a [CoordinatorLayout] to hide the view off the\n * bottom of the screen when scrolling down, and show it when scrolling up.\n *\n * SPECIFICALLY FOR [BottomNavigationView][com.google.android.material.bottomnavigation.BottomNavigationView].\n *\n * @author Yenaly Liew\n * @time 2022/07/19 019 12:18\n */\nopen class YenalyHideBottomViewOnScrollBehavior<V : View> : CoordinatorLayout.Behavior<V> {\n\n    private var height = 0\n    private var currentState: Int = STATE_SCROLLED_DOWN\n    private var additionalHiddenOffsetY = 0\n    private var currentAnimator: ViewPropertyAnimator? = null\n\n    constructor() : super()\n\n    constructor(\n        context: Context,\n        attrs: AttributeSet?\n    ) : super(context, attrs)\n\n    override fun onLayoutChild(\n        parent: CoordinatorLayout, child: V, layoutDirection: Int\n    ): Boolean {\n        val paramsCompat = child.layoutParams as MarginLayoutParams\n        height = child.measuredHeight + paramsCompat.bottomMargin\n        return super.onLayoutChild(parent, child, layoutDirection)\n    }\n\n    /**\n     * Sets an additional offset for the y position used to hide the view.\n     *\n     * @param child the child view that is hidden by this behavior\n     * @param offset the additional offset in pixels that should be added when the view slides away\n     */\n    fun setAdditionalHiddenOffsetY(child: V, @Dimension offset: Int) {\n        additionalHiddenOffsetY = offset\n        if (currentState == STATE_SCROLLED_DOWN) {\n            child.translationY = (height + additionalHiddenOffsetY).toFloat()\n        }\n    }\n\n    override fun onStartNestedScroll(\n        coordinatorLayout: CoordinatorLayout,\n        child: V,\n        directTargetChild: View,\n        target: View,\n        nestedScrollAxes: Int,\n        type: Int\n    ): Boolean {\n        return nestedScrollAxes == ViewCompat.SCROLL_AXIS_VERTICAL\n    }\n\n    override fun onNestedScroll(\n        coordinatorLayout: CoordinatorLayout,\n        child: V,\n        target: View,\n        dxConsumed: Int,\n        dyConsumed: Int,\n        dxUnconsumed: Int,\n        dyUnconsumed: Int,\n        type: Int,\n        consumed: IntArray\n    ) {\n        if (dyConsumed > 0) {\n            slideDown(child)\n        } else if (dyConsumed < 0) {\n            slideUp(child)\n        }\n    }\n\n    /** Returns true if the current state is scrolled up.  */\n    val isScrolledUp: Boolean\n        get() = currentState == STATE_SCROLLED_UP\n\n    /**\n     * Performs an animation that will slide the child from it's current position to be totally on the\n     * screen.\n     */\n    @JvmOverloads\n    fun slideUp(child: V, animate: Boolean =  /*animate=*/true) {\n        if (isScrolledUp) {\n            return\n        }\n        if (currentAnimator != null) {\n            currentAnimator!!.cancel()\n            child.clearAnimation()\n        }\n        currentState = STATE_SCROLLED_UP\n        val targetTranslationY = 0\n        if (animate) {\n            animateChildTo(\n                child,\n                targetTranslationY,\n                ENTER_ANIMATION_DURATION.toLong(),\n                LINEAR_OUT_SLOW_IN_INTERPOLATOR\n            )\n        } else {\n            child.translationY = targetTranslationY.toFloat()\n        }\n    }\n\n    /** Returns true if the current state is scrolled down.  */\n    val isScrolledDown: Boolean\n        get() = currentState == STATE_SCROLLED_DOWN\n\n    /**\n     * Performs an animation that will slide the child from it's current position to be totally off\n     * the screen.\n     */\n    @JvmOverloads\n    fun slideDown(child: V, animate: Boolean =  /*animate=*/true) {\n        if (isScrolledDown) {\n            return\n        }\n        if (currentAnimator != null) {\n            currentAnimator!!.cancel()\n            child.clearAnimation()\n        }\n        currentState = STATE_SCROLLED_DOWN\n        val targetTranslationY = height + additionalHiddenOffsetY\n        if (animate) {\n            animateChildTo(\n                child,\n                targetTranslationY,\n                EXIT_ANIMATION_DURATION.toLong(),\n                FAST_OUT_LINEAR_IN_INTERPOLATOR\n            )\n        } else {\n            child.translationY = targetTranslationY.toFloat()\n        }\n    }\n\n    private fun animateChildTo(\n        child: V, targetY: Int, duration: Long, interpolator: TimeInterpolator\n    ) {\n        currentAnimator = child\n            .animate()\n            .translationY(targetY.toFloat())\n            .setInterpolator(interpolator)\n            .setDuration(duration)\n            .setListener(\n                object : AnimatorListenerAdapter() {\n                    override fun onAnimationEnd(animation: Animator) {\n                        currentAnimator = null\n                    }\n                })\n    }\n\n    companion object {\n        protected const val ENTER_ANIMATION_DURATION = 225\n        protected const val EXIT_ANIMATION_DURATION = 175\n        private const val STATE_SCROLLED_DOWN = 1\n        private const val STATE_SCROLLED_UP = 2\n\n        private val FAST_OUT_LINEAR_IN_INTERPOLATOR: TimeInterpolator =\n            FastOutLinearInInterpolator()\n\n        private val LINEAR_OUT_SLOW_IN_INTERPOLATOR: TimeInterpolator =\n            LinearOutSlowInInterpolator()\n    }\n}"
  },
  {
    "path": "yenaly_libs/src/main/res/anim/yenaly_bottom_to_top_in.xml",
    "content": "<?xml version=\"1.0\" encoding=\"utf-8\"?>\n\n<set xmlns:android=\"http://schemas.android.com/apk/res/android\">\n    <translate\n        android:duration=\"700\"\n        android:fromYDelta=\"100%p\"\n        android:toYDelta=\"0\" />\n    <alpha\n        android:duration=\"700\"\n        android:fromAlpha=\"0.0\"\n        android:toAlpha=\"1.0\" />\n</set>"
  },
  {
    "path": "yenaly_libs/src/main/res/anim/yenaly_bottom_to_top_out.xml",
    "content": "<?xml version=\"1.0\" encoding=\"utf-8\"?>\n\n<set xmlns:android=\"http://schemas.android.com/apk/res/android\">\n    <translate\n        android:duration=\"700\"\n        android:fromYDelta=\"0\"\n        android:toYDelta=\"-100%p\" />\n    <alpha\n        android:duration=\"700\"\n        android:fromAlpha=\"1.0\"\n        android:toAlpha=\"0.0\" />\n</set>"
  },
  {
    "path": "yenaly_libs/src/main/res/anim/yenaly_top_to_bottom_in.xml",
    "content": "<?xml version=\"1.0\" encoding=\"utf-8\"?>\n\n<set xmlns:android=\"http://schemas.android.com/apk/res/android\">\n    <translate\n        android:duration=\"700\"\n        android:fromYDelta=\"-100%p\"\n        android:toYDelta=\"0\" />\n    <alpha\n        android:duration=\"700\"\n        android:fromAlpha=\"0.0\"\n        android:toAlpha=\"1.0\" />\n</set>"
  },
  {
    "path": "yenaly_libs/src/main/res/anim/yenaly_top_to_bottom_out.xml",
    "content": "<?xml version=\"1.0\" encoding=\"utf-8\"?>\n\n<set xmlns:android=\"http://schemas.android.com/apk/res/android\">\n    <translate\n        android:duration=\"700\"\n        android:fromYDelta=\"0\"\n        android:toYDelta=\"100%p\" />\n    <alpha\n        android:duration=\"700\"\n        android:fromAlpha=\"1.0\"\n        android:toAlpha=\"0.0\" />\n</set>"
  },
  {
    "path": "yenaly_libs/src/main/res/layout/yenaly_activity_crash_dialog.xml",
    "content": "<?xml version=\"1.0\" encoding=\"utf-8\"?>\n<FrameLayout xmlns:android=\"http://schemas.android.com/apk/res/android\"\n    xmlns:tools=\"http://schemas.android.com/tools\"\n    android:layout_width=\"wrap_content\"\n    android:layout_height=\"wrap_content\"\n    tools:ignore=\"UselessLeaf\">\n\n</FrameLayout>"
  },
  {
    "path": "yenaly_libs/src/main/res/layout/yenaly_activity_settings.xml",
    "content": "<layout xmlns:android=\"http://schemas.android.com/apk/res/android\"\n    xmlns:app=\"http://schemas.android.com/apk/res-auto\">\n\n    <data class=\"YenalySettingsDataBinding\">\n\n    </data>\n\n    <LinearLayout\n        android:layout_width=\"match_parent\"\n        android:layout_height=\"match_parent\"\n        android:orientation=\"vertical\">\n\n        <androidx.appcompat.widget.Toolbar\n            android:id=\"@+id/settings_toolbar\"\n            android:layout_width=\"match_parent\"\n            android:layout_height=\"?attr/actionBarSize\"\n            android:background=\"?attr/colorPrimaryVariant\"\n            android:theme=\"@style/ThemeOverlay.Material3.Dark\"\n            app:popupTheme=\"@style/ThemeOverlay.Material3.Light\"\n            app:title=\"@string/yenaly_settings\" />\n\n        <FrameLayout\n            android:id=\"@+id/settings_container_view\"\n            android:layout_width=\"match_parent\"\n            android:layout_height=\"match_parent\" />\n\n    </LinearLayout>\n\n</layout>"
  },
  {
    "path": "yenaly_libs/src/main/res/layout/yenaly_dialog_loading.xml",
    "content": "<?xml version=\"1.0\" encoding=\"utf-8\"?>\n<LinearLayout xmlns:android=\"http://schemas.android.com/apk/res/android\"\n    xmlns:tools=\"http://schemas.android.com/tools\"\n    android:layout_width=\"wrap_content\"\n    android:layout_height=\"wrap_content\"\n    android:gravity=\"center\"\n    android:orientation=\"horizontal\"\n    android:padding=\"24dp\">\n\n    <ProgressBar\n        android:layout_width=\"wrap_content\"\n        android:layout_height=\"wrap_content\" />\n\n    <TextView\n        android:id=\"@+id/loading_text\"\n        android:layout_width=\"wrap_content\"\n        android:layout_height=\"wrap_content\"\n        android:layout_marginStart=\"8dp\"\n        android:textSize=\"16sp\"\n        tools:text=\"正在加载中...\" />\n\n\n</LinearLayout>"
  },
  {
    "path": "yenaly_libs/src/main/res/layout/yenaly_preference_switch_widget.xml",
    "content": "<?xml version=\"1.0\" encoding=\"utf-8\"?>\n<com.google.android.material.materialswitch.MaterialSwitch xmlns:android=\"http://schemas.android.com/apk/res/android\"\n    android:id=\"@+id/switchWidget\"\n    android:layout_width=\"wrap_content\"\n    android:layout_height=\"wrap_content\"\n    android:background=\"@null\"\n    android:clickable=\"false\"\n    android:focusable=\"false\" />"
  },
  {
    "path": "yenaly_libs/src/main/res/values/ids.xml",
    "content": "<?xml version=\"1.0\" encoding=\"utf-8\"?>\n<resources xmlns:tools=\"http://schemas.android.com/tools\" tools:ignore=\"ResourceName\">\n\n    <item name=\"statusbarutil_fake_status_bar_view\" type=\"id\" />\n    <item name=\"statusbarutil_translucent_view\" type=\"id\" />\n\n</resources>"
  },
  {
    "path": "yenaly_libs/src/main/res/values/strings.xml",
    "content": "<?xml version=\"1.0\" encoding=\"utf-8\"?>\n<resources>\n    <string name=\"yenaly_settings\">设置</string>\n    <string name=\"yenaly_error_title\">如果可以，给开发者反馈一下！</string>\n    <string name=\"yenaly_restart_app\">重启软件</string>\n    <string name=\"yenaly_exit_app\">关闭软件</string>\n    <string name=\"yenaly_loading\">正在加载中</string>\n    <string name=\"yenaly_copy_to_clipboard\">复制到剪贴板</string>\n    <string name=\"yenaly_copy\">复制</string>\n</resources>"
  },
  {
    "path": "yenaly_libs/src/main/res/values/styles.xml",
    "content": "<?xml version=\"1.0\" encoding=\"utf-8\"?>\n<resources>\n\n    <style name=\"YenalyDialog\" parent=\"Theme.Material3.DayNight.Dialog\">\n        <item name=\"android:windowBackground\">@android:color/transparent</item>\n        <item name=\"android:windowFrame\">@null</item>\n        <item name=\"windowNoTitle\">true</item>\n        <item name=\"android:windowIsFloating\">true</item>\n        <item name=\"android:windowIsTranslucent\">true</item>\n        <item name=\"android:windowContentOverlay\">@null</item>\n        <item name=\"android:windowAnimationStyle\">@android:style/Animation.Dialog</item>\n        <item name=\"android:backgroundDimEnabled\">true</item>\n    </style>\n\n    <style name=\"YenalyBottomSheetStyleWrapper\" parent=\"Widget.Design.BottomSheet.Modal\">\n        <item name=\"android:background\">@android:color/transparent</item>\n    </style>\n\n    <style name=\"YenalyBottomSheetDialog\" parent=\"Theme.Design.Light.BottomSheetDialog\">\n        <item name=\"bottomSheetStyle\">@style/YenalyBottomSheetStyleWrapper</item>\n    </style>\n\n</resources>"
  },
  {
    "path": "yenaly_libs/src/main/res/xml/http_network_config.xml",
    "content": "<?xml version=\"1.0\" encoding=\"utf-8\"?>\n<network-security-config xmlns:tools=\"http://schemas.android.com/tools\"\n    tools:ignore=\"ResourceName\">\n    <base-config\n        cleartextTrafficPermitted=\"true\"\n        tools:ignore=\"InsecureBaseConfiguration\" />\n</network-security-config>"
  },
  {
    "path": "yenaly_libs/src/test/java/com/yenaly/yenaly_libs/UtilUnitTest.kt",
    "content": "package com.yenaly.yenaly_libs\n\nimport com.yenaly.yenaly_libs.utils.folderSize\nimport com.yenaly.yenaly_libs.utils.formatFileSizeV2\nimport com.yenaly.yenaly_libs.utils.md5\nimport com.yenaly.yenaly_libs.utils.secondToTimeCase\nimport org.junit.Assert.assertEquals\nimport org.junit.Test\nimport java.io.File\n\n/**\n * Example local unit test, which will execute on the development machine (host).\n *\n * See [testing documentation](http://d.android.com/tools/testing).\n */\nclass UtilUnitTest {\n    @Test\n    fun formatFileSizeV2_isCorrect() {\n        // Test with default parameters\n        assertEquals(\"1 kB\", 1000L.formatFileSizeV2(useSi = true))\n        assertEquals(\"1 KiB\", 1024L.formatFileSizeV2())\n\n        // Test with different decimal places\n        assertEquals(\n            \"1.00 kB\",\n            1000L.formatFileSizeV2(useSi = true, decimalPlaces = 2, stripTrailingZeros = false)\n        )\n        assertEquals(\n            \"1.00 KiB\",\n            1024L.formatFileSizeV2(decimalPlaces = 2, stripTrailingZeros = false)\n        )\n\n        // Test with stripTrailingZeros = false\n        assertEquals(\"1.0 kB\", 1000L.formatFileSizeV2(useSi = true, stripTrailingZeros = false))\n        assertEquals(\"1.0 KiB\", 1024L.formatFileSizeV2(stripTrailingZeros = false))\n\n        // Test with different sizes\n        assertEquals(\n            \"1.0 MB\",\n            1_000_000L.formatFileSizeV2(useSi = true, stripTrailingZeros = false)\n        )\n        assertEquals(\"1.0 MiB\", 1_048_576L.formatFileSizeV2(stripTrailingZeros = false))\n        assertEquals(\n            \"1.0 GB\",\n            1_000_000_000L.formatFileSizeV2(useSi = true, stripTrailingZeros = false)\n        )\n        assertEquals(\"1.0 GiB\", 1_073_741_824L.formatFileSizeV2(stripTrailingZeros = false))\n\n        // Test with edge cases\n        assertEquals(\"999 B\", 999L.formatFileSizeV2(useSi = true))\n        assertEquals(\"1023 B\", 1023L.formatFileSizeV2())\n        assertEquals(\"1.0 kB\", 1000L.formatFileSizeV2(useSi = true, stripTrailingZeros = false))\n        assertEquals(\"1.0 KiB\", 1024L.formatFileSizeV2(stripTrailingZeros = false))\n    }\n\n    @Test\n    fun secondToTimeCase_isCorrect() {\n        assertEquals(\"02:03\", 123L.secondToTimeCase())\n        assertEquals(\"00:09\", 9L.secondToTimeCase())\n        assertEquals(\"09:00\", 540L.secondToTimeCase())\n        assertEquals(\"01:00:00\", 3600L.secondToTimeCase())\n        assertEquals(\"00:00\", 0L.secondToTimeCase())\n        assertEquals(\"00:59\", 59L.secondToTimeCase())\n        assertEquals(\"01:01:01\", 3661L.secondToTimeCase())\n    }\n\n    @Test\n    fun md5_calculatesCorrectHashForNonEmptyFile() {\n        val file = File.createTempFile(\"testFile\", \".txt\").apply {\n            writeText(\"Hello, World!\")\n        }\n        assertEquals(\"65a8e27d8879283831b664bd8b7f0ad4\", file.md5())\n        file.delete()\n    }\n\n    @Test\n    fun md5_calculatesCorrectHashForEmptyFile() {\n        val file = File.createTempFile(\"emptyFile\", \".txt\")\n        assertEquals(\"d41d8cd98f00b204e9800998ecf8427e\", file.md5())\n        file.delete()\n    }\n\n    @Test\n    fun md5_calculatesCorrectHashForLargeFile() {\n        val file = File.createTempFile(\"largeFile\", \".txt\").apply {\n            writeText(\"a\".repeat(10_000_000))\n        }\n        assertEquals(\"7095bae098259e0dda4b7acc624de4e2\", file.md5())\n        file.delete()\n    }\n\n    @Test\n    fun folderSize_calculatesCorrectSizeForNonEmptyFolder() {\n        val folder = File.createTempFile(\"testFolder\", \"\").apply {\n            delete()\n            mkdir()\n            File(this, \"file1.txt\").writeText(\"Hello\")\n            File(this, \"file2.txt\").writeText(\"World\")\n        }\n        assertEquals(10L, folder.folderSize)\n        folder.deleteRecursively()\n    }\n\n    @Test\n    fun folderSize_calculatesCorrectSizeForEmptyFolder() {\n        val folder = File.createTempFile(\"emptyFolder\", \"\").apply {\n            delete()\n            mkdir()\n        }\n        assertEquals(0L, folder.folderSize)\n        folder.deleteRecursively()\n    }\n\n    @Test\n    fun folderSize_calculatesCorrectSizeForNestedFolders() {\n        val folder = File.createTempFile(\"nestedFolder\", \"\").apply {\n            delete()\n            mkdir()\n            File(this, \"file1.txt\").writeText(\"Hello\")\n            File(this, \"subFolder\").apply {\n                mkdir()\n                File(this, \"file2.txt\").writeText(\"World\")\n            }\n        }\n        assertEquals(10L, folder.folderSize)\n        folder.deleteRecursively()\n    }\n\n    @Test\n    fun folderSize_returnsZeroForNullFile() {\n        val folder: File? = null\n        assertEquals(0L, folder.folderSize)\n    }\n}"
  }
]